线程笔记

基本概念

1.什么是进程?

  一个进程对应一个应用程序,例如,在Windows下启动一个word,在java开发环境下启动JVM,就表示启动了一个进程。现代计算机都是支持多进程的,在一个操作系统下可以同时启动多个进程。

2.多进程有什么用?

  单进程计算机只能做一件事

  一边玩游戏(游戏进程)一边听歌(音乐进程),对于单核计算机,游戏进程和音乐进程是同时运行的吗?不是。因为CPU在某一个时间点上只能做一件事情,计算机在两个进程间频繁的切换执行,切换速度极快,让人感觉是在同时运行。

  多进程不是提高执行速度,而是提高CPU的使用率。

  进程和进程间的内存是独立的

3.什么是线程?

  线程是一个进程中的执行场景,一个进程可以启动多个线程。

  多线程不是为了提高执行速度,是为了提高应用程序的使用率

  线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈

4.java程序的运行原理?

  java命令会启动java虚拟机,等同于启动了一个应用程序,表示启动了一个进程。该进程会自动启动一个主线程,然后主线程会自动调用某个类的main方法,所以main方法运行在主线程中

线程的创建和启动

1.第一种方式

  继承Thread——>重写run方法

public class ThreadTest01 {

    public static void main(String[] args) {
        Thread t = new Thread01();  //创建线程
        //启动线程
        t.start(); //这段代码执行瞬间结束,告诉JVM再分配一个栈给t线程
                        //run()不需要程序员去调用,线程启动后自动调用run,如果直接调用run则不会开启新的线程
        
        for (int i = 0 ; i < 100 ; i++){
            System.out.println("main--->" + i);
        }
        //在多线程中,main方法结束只是主线程栈中没有方法栈帧了,
        //但是其他线程或者其他栈中可能还有
        //所以,main方法结束,程序可能还在运行
    }
    
}
class Thread01 extends Thread{
    @Override
    public void run() {
        for (int i = 0 ; i<100 ; i++){
            System.out.println("run--->" +i);
        }
    }
}

一部分结果

main--->61
run--->70
main--->62
run--->71
main--->63
run--->72
run--->73
run--->74

2.第二种方式

  写一个Runnable接口的实现类——>实现run方法

public class ThreadTest02 {
    public static void main(String[] args) {
        Thread t = new Thread(new Thread02());
        t.start();
    }
}
class Thread02 implements Runnable{
    @Override
    public void run() {
        //do something
    }
}

推荐第二种方式,因为实现接口还可以保留类的继承,因为java是单继承的

 线程的生命周期

1.新建。new出线程

2.就绪。t.start(),该状态表示该线程有权利去获得CPU的时间片,当拿到时间片后就马上执行run方法,这个时候就进入运行状态。

3.运行。run方法执行。

4.阻塞。运行中可能遇到了阻塞时间,进入阻塞状态,等到阻塞接触后,会回到就绪状态

5.消亡。run方法执行结束

 线程的调度与控制

  JVM负责线程的调度,取得CPU的使用权。目前有两种调度模型,分时调度和抢占式调度,java采用抢占式调度。

  分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片

  抢占式:优先级高的线程获得的CPU的使用权相对多些,如果优先级相同,则随机选一个

1.优先级

  从1~10,默认5

2.sleep()

  是一个静态方法,阻塞当前线程,腾出CPU让给其他线程

  这个方法要捕获异常,注意Thread的run方法不抛出异常,所以在复写run方法之后,在run方法的声明位置不能使用throws关键字,所以run中的异常只能try/catch

   如何打断线程的休眠? 

public class SleepTest {
    
    public static void main(String[] args) throws Exception{
        //依靠异常处理机制来终止了一个线程的休眠
        //启动线程5s后打断休眠
        Thread t = new Thread(new T());
        
        t.start();
        t.setName("t");
        Thread.sleep(5000);
        
        t.interrupt();
        System.out.println("sleep over");
    }
}

class T implements Runnable{
    public void run(){ 
        try{
            Thread.sleep(100000);
            
            System.out.println("hello world~");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        
        for (int i=0 ; i < 10 ; i ++){
            System.out.println(Thread.currentThread().getName() + "---->" + i);
        }
    }
}

依靠异常处理机制来终止了一个线程的休眠,run里的hello world不会输出,下面的for会正常输出

一个终止线程的方法?

public class SleepTest {
    
    public static void main(String[] args) throws Exception{    
        //线程运行5s后终止
        Processor p = new Processor();
        Thread t = new Thread(p);
        t.setName("t");
        t.start();
        Thread.sleep(5000);
        p.run = false;
        System.out.println("main over");
    }
}

class Processor implements Runnable{
    boolean run = true;
    public void run(){
        for(int i = 0 ; i<10 ; i++){
            if (run){
                try{
                    Thread.sleep(1000);
                }catch(Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "---->" + i);
            }else {
                return;
            }
        }
        System.out.println("for over");
    }
}
3.yield

静态方法,给同一个优先级的线程让位,但是让位之间不固定,和sleep不同的是不能跟指定时间。

4.join

成员方法,线程的合并。

public class JoinTest {
    public static void main(String[] args) throws InterruptedException{
        Thread t = new Thread(new JoinThread());
        t.setName("t");
        t.start();
        //合并线程
        t.join(); //t和主线程合并,也就是说两个栈空间变成了一个栈空间,变成单线程了
        
        for (int i = 0 ; i < 10 ; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

class JoinThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0 ; i<5 ; i++){
            try{
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()    + "--->" + i);
        }
    }
}

t--->0
t--->1
t--->2
t--->3
t--->4
main--->0
main--->1
main--->2
……

 线程的同步!!!!!

  关于异步编程模型和同步编程模型的区别

  异步:t1执行t1的,t2执行t2的,两个线程谁也不等谁

  同步:两个线程执行,t1必须等t2执行结束才能执行

1.为什么要引入线程同步?

  为了数据安全,尽管应用程序的使用率降低,线程同步机制使程序变成了单线程

2.什么时候要同步?

  1.多线程环境。2.多线程环境共享同一数据。3.共享的数据设计修改操作。<三个条件缺一不可>

3.synchronized
synchronized(this){  //括号里面放同步对象
                //将需要同步的代码放在这里,这块代码只会有一个线程执行
            }

原理:t1、t2两个线程,当t1遇到synchronized的时候就会去找()里面对象的对象锁,任何一个java对象上都有对象锁,其实就是每个对象都有1和0这个标识,如果找到了则进入代码块执行代码,代码块中代码执行完毕时归还对象锁,如果在还没执行完毕的时候,t2也执行到这里,遇到了这个关键字,但是找不到对象锁,所以只能等待对象锁的归还。

synchronized加到成员方法上,线程拿走的也是this的对象锁。不过这种写法就意味着整个方法里的代码都需要同步,可能造成不应该同步的代码也同步了,进一步降低了执行效率,所以不如上面那种方式控制得精确。

public synchronized void fun(){

    
}

查看源码可以发现StringBuffer的每个方法都有synchronized,所以StringBuffer是线程安全的。Vector,HashTable都是线程安全的。

类锁:类只有一个,所以类锁也只有一个

当synchronized添加到静态方法上,线程执行到此方法的时候会找类锁。

public class ThreadTest03 {

    public static void main(String[] args) throws Exception{
        Thread t1 = new Thread(new Thread03());
        Thread t2 = new Thread(new Thread03());
        t1.setName("t1");
        t2.setName("t2");
        
        t1.start();
        //保证t1先执行
        Thread.sleep(1000);
        t2.start();
    }
}

class Thread03 implements Runnable {
    
    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("t1"))
            MyClass.fun1();  //通过类名直接调用,当然这里如果用对象的引用来调用,还是一样的结果,因为是静态方法
        if (Thread.currentThread().getName().equals("t2"))
            MyClass.fun2();
    }
}

class MyClass{
    public synchronized static void fun1(){//类锁
        try {
            Thread.sleep(5000);
        } catch (Exception e){
            e.printStackTrace();
        }
        System.out.println("fun1---->");
    }
    
    //没加synchronized,所以不需要类锁,所以t2在执行fun2是不会等fun1的锁(类锁)
    //但是如果加上的话,就需要等t1执行完毕才能执行,因为类锁只有一个!
    public static void fun2() {  
        System.out.println("fun2---->");
    }
}
4.死锁

比如说有一种情况,两个线程t1,t2。两个对象o1,o2。两个线程都要锁这两个对象,t1锁掉o1后,t2把o2锁了,之后t1想继续锁o2,就得等t2归还o2的锁,但是t2也想在锁到o2后继续锁o1,在没锁到o1的的时候是不会交出o2的锁的,于是这个局面就僵持住了。

public class ThreadTest04 {

    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();
        //两个线程共享两个对象
        Thread t1 = new Thread(new Thread05(o1 , o2));
        Thread t2 = new Thread(new Thread06(o1 , o2));
        
        t1.start();
        t2.start();
    }
}

class Thread05 implements Runnable {
    Object o1 ;
    Object o2 ;
    public Thread05(Object o1 , Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run() {
        synchronized (o1){
            try {
                Thread.sleep(1000);  //休眠保证o2被锁到
            } catch (Exception e){
                e.printStackTrace();
            }
            
            synchronized (o2){
                System.out.println("no dead~");
            }
        }
    }
}

class Thread06 implements Runnable {
    Object o1 ;
    Object o2 ;
    public Thread06(Object o1 , Object o2){
        this.o1 = o1;
        this.o2 = o2;
    }
    @Override
    public void run() {
        synchronized (o2){
            try {//这里如果不休眠的话,不能保证一定死锁!
                Thread.sleep(1000);
            } catch (Exception e){
                e.printStackTrace();
            } 
            synchronized (o1){
                System.out.println("no dead~");
            }
        }
    }
}
5.举个同步的例子
public class TicketDemo {

    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread t1 = new Thread (saleTicket , "t1");
        Thread t2 = new Thread (saleTicket , "t2");
        
        t1.start();
        t2.start();
    }

}

class SaleTicket implements Runnable {
    int num = 50;
    
    @Override
    public void run() {
        
        while (true){
            try {
                Thread.sleep(50);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            
            if ( num <= 0)
                break;
            
            System.out.println(Thread.currentThread().getName() + "---已卖出--->" + num);
            num--;
        }
    }
}

简单实现一个卖票的程序,两个线程共享一个资源---票,会出现类似这样的输出

窗口t2---卖出一张--->26
窗口t1---卖出一张--->24
窗口t2---卖出一张--->24
窗口t1---卖出一张--->22
窗口t2---卖出一张--->21
窗口t1---卖出一张--->20

有两个24显然是不对的,这时就需要将要卖票部分的代码做一个同步

synchronized (this){
                if ( num <= 0)
                    break;
                
                System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num);
                num--;
            }

窗口t1---卖出一张--->7
窗口t1---卖出一张--->6
窗口t2---卖出一张--->5
窗口t2---卖出一张--->4
窗口t1---卖出一张--->3
窗口t1---卖出一张--->2
窗口t2---卖出一张--->1

这样就正常了,下面换一个Lock的实现方法,lock定义成SaleTicket的成员变量

lock.lock();
                if ( num <= 0)
                    break;
                
                System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num);
                num--;
            lock.unlock();

输出是正常的,但是!!!有一个严重的问题,程序没有终止!!!为什么呢?考虑这样一种情况,t1锁上后开始往下执行,t2在锁那里等着,t1发现num<=0了,跳出循环,锁呢?并没有释放掉,那t2就要等到海枯石烂了。

所以这里要保证在需要同步的代码块执行完毕之后一定释放掉锁!!!这也是和synchronized的一个区别,synchronized是默认执行完后归还锁,是一种隐式的释放,lock是显示的。所以这种写法是不行的,要用 finally!

lock.lock();
            try {
                if ( num <= 0)
                    break;
                
                System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num);
                num--;
            } finally {
                lock.unlock();
            }

 这样程序就正常结束了

比较下Lock和synchronized:

synchronized有两种用法:

1.同步代码块的形式

synchronized(对象){ //锁上(隐式)
    //要同步的代码块
}//释放锁(隐式)

2.同步方法

public synchronized void method(){
  //this锁上(隐式)
//要同步的代码  
}//this释放(隐式)

 

守护线程

线程分两种:用户线程、守护线程。通常用到的就是用户线程,而例如java的垃圾回收器就是一个守护线程。只有全部用户线程都结束了,守护线程才会结束。

JVM启动一个主线程,还有一个垃圾回收线程,两个不同的栈。

public class DaemonThread {
    public static void main(String[] args) throws Exception{
        Thread t = new Thread(new Daemon());
        t.setName("t");
        t.setDaemon(true);
        t.start();
        
        for(int i = 0 ; i < 10 ; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
            Thread.sleep(1000);
        }
    }
    
}

class Daemon implements Runnable{
    
    public void run(){
        int i = 0;
        while(true){
            System.out.println(Thread.currentThread().getName() + "--->" + i++);
            try{
                Thread.sleep(1000);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

上面代码中,线程t里面的run()是一个无限循环,但是这里将他设置成一个守护线程,而用户线程只有主线程,所以当主线程结束后,守护线程会自动结束,不会无限循环下去。

定时器

作用:每隔一段固定的时间执行一段代码

public class TimerTest {

    public static void main(String[] args) throws Exception{
        Timer t = new Timer();
        t.schedule(
                new LogTask(),
                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-09-27 21:22:00")
                , 3*1000);
    }
}

class LogTask extends TimerTask{
        @Override
        public void run() {
            System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
        }
}

2016-09-27 21:22:00
2016-09-27 21:22:03
2016-09-27 21:22:06
2016-09-27 21:22:09

sleep(),wait(),notify(),yield()总结

sleep不会归还对象锁,需要捕获异常,可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

yield与sleep类似,只是不能指定时间,只能让同优先级的线程有执行的机会

wait当前线程暂停并释放对象锁,当前线程被放入对象等待池中,当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志。如果锁标志等待池中没有线程,则notify()不起作用。

wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

Thread类的方法:sleep(),yield()
Object的方法:wait()和notify()
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,否则会抛出异常,而sleep可以在任何地方使用 
sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常 
 
原文地址:https://www.cnblogs.com/i-love-kobe/p/5904303.html