多线程基础

1.并发、并⾏、进程、线程

并发与并⾏

  • 并发:指两个或多个事件在同⼀个时间段内发⽣。
  • 并⾏:指两个或多个事件在同⼀时刻发⽣(同时发⽣)。

在操作系统中同时运行多个程序,在单核CPU系统中看似是同一时刻在运行不同的程序,实际是每一时刻只有一个程序在运行。在系统运行时,会在CPU上为不同的程序分配不同的时间片,运行的程序获取到时间片时才是真正的运行,不同时间片切换称之为上下文切换,上下文切换的时间很短十几毫秒甚至几毫秒就完成,所以给人视觉感觉是同时运行。

多核CPU系统中,这些同时运行的程序会分配到不同的处理器上执行,实现多任务并行执行。这样可以大大的提高运行效率。

线程与进程

  • 进程:是指⼀个内存中运⾏的应⽤程序,每个进程都有⼀个独⽴的内存空间,⼀个应⽤程序可以同时运⾏多个进程;进程也是程序的⼀次执⾏过程,是系统运⾏程序的基本单位;系统运⾏⼀个程序即是⼀个进程从创建、运⾏到消亡的过程。
  • 线程:线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏,⼀个进程中⾄少有⼀个线程。⼀个进程中是可以有多个线程的,这个应⽤程序也可以称之为多线程程序。

线程调度

  • 分时调度:所有线程轮流使⽤ CPU 的使⽤权,平均分配每个线程占⽤ CPU 的时间。
  • 抢占式调度:优先让优先级⾼的线程使⽤ CPU,如果线程的优先级相同,那么会随机选择⼀个(线程随机性),Java使⽤的为抢占式调度

2.创建线程

(1)继承Thread类

public class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}
public static void main(String[] args) {
     MyThread myThread = new MyThread();
     myThread.start();
     System.out.println(Thread.currentThread().getName()+": main方法执行!");
}

 执行结果为:

 

把start方法换成run方法再执行一次 

public static void main(String[] args) {
    MyThread myThread = new MyThread();
    myThread.run();
    System.out.println(Thread.currentThread().getName()+": main方法执行!");
}

结论:在执行线程的时候不可以使用run()方法执行线程,要使用start()方法。使用run方法,会使用main线程按照代码先后顺序执行,MyThread代码执行不受影响,但是失去了新建线程的意义。

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

    private native void start0();

 在Thread源码中start()方法调用了start0(),start0()是个native方法调用的jvm,其实在JVM内部调用的是JavaThread方法,JavaThread又调用了操作系统PThread_create创建线程。

(2)实现Runnable接口

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}
public static void main(String[] args) {
    MyRunnable myRunnable = new MyRunnable();
    Thread t1 = new Thread(myRunnable);
    t1.setName("新建runnable线程");
    t1.start();
    System.out.println(Thread.currentThread().getName()+": main方法执行!");
}

 

继承Thread和实现Runnable的区别:

1.继承Runnable,不适合资源共享。实现Runnable很容易实现资源共享。

2.实现Runnable可以避免java的单继承的局限性。

3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程是独立的。

public class MyThread extends Thread{

    private int stick = 20;
    @Override
    public void run() {
        while(stick>0){
            System.out.println(Thread.currentThread().getName()+": "+stick --);
        }
    }
}
public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.setName("新建线程1");
        myThread1.start();
        MyThread myThread2 = new MyThread();
        myThread2.setName("新建线程2");
        myThread2.start();
        MyThread myThread3 = new MyThread();
        myThread3.setName("新建线程3");
        myThread3.start();
}
新建线程1: 20
新建线程1: 19
新建线程1: 18
新建线程1: 17
新建线程1: 16
新建线程1: 15
新建线程1: 14
新建线程1: 13
新建线程1: 12
新建线程1: 11
新建线程1: 10
新建线程1: 9
新建线程1: 8
新建线程1: 7
新建线程1: 6
新建线程1: 5
新建线程1: 4
新建线程1: 3
新建线程1: 2
新建线程1: 1
新建线程2: 20
新建线程2: 19
新建线程2: 18
新建线程2: 17
新建线程2: 16
新建线程2: 15
新建线程2: 14
新建线程2: 13
新建线程3: 20
新建线程3: 19
新建线程3: 18
新建线程3: 17
新建线程3: 16
新建线程3: 15
新建线程2: 12
新建线程3: 14
新建线程3: 13
新建线程3: 12
新建线程3: 11
新建线程3: 10
新建线程3: 9
新建线程3: 8
新建线程3: 7
新建线程3: 6
新建线程3: 5
新建线程3: 4
新建线程3: 3
新建线程3: 2
新建线程3: 1
新建线程2: 11
新建线程2: 10
新建线程2: 9
新建线程2: 8
新建线程2: 7
新建线程2: 6
新建线程2: 5
新建线程2: 4
新建线程2: 3
新建线程2: 2
新建线程2: 1

从以上运行结果可以看出3个线程都是从20开始做--运算,每个线程都执行了20次。

public class MyRunnable implements Runnable{
    private int stick = 20;
    @Override
    public void run() {
        while(stick>0){
            System.out.println(Thread.currentThread().getName()+": "+stick --);
        }
    }
}
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        t1.setName("新建runnable线程1");
        t1.start();
        Thread t2 = new Thread(myRunnable);
        t2.setName("新建runnable线程2");
        t2.start();
        Thread t3 = new Thread(myRunnable);
        t3.setName("新建runnable线程3");
        t3.start();
    }
新建runnable线程1: 20
新建runnable线程2: 19
新建runnable线程3: 17
新建runnable线程1: 18
新建runnable线程1: 14
新建runnable线程1: 13
新建runnable线程1: 12
新建runnable线程3: 15
新建runnable线程2: 16
新建runnable线程2: 9
新建runnable线程2: 8
新建runnable线程2: 7
新建runnable线程2: 6
新建runnable线程2: 5
新建runnable线程2: 4
新建runnable线程2: 3
新建runnable线程2: 2
新建runnable线程2: 1
新建runnable线程3: 10
新建runnable线程1: 11

从以上运行结果可以看出3个线程做--运算,基数是一个数,不是3个。

结论:实现Runnable更适合资源共享。

其实继承Thread也可以实现资源共享,要把stick变量定义成static

public class MyThread extends Thread{

    private static int stick = 20;
    @Override
    public void run() {
        while(stick>0){
            System.out.println(Thread.currentThread().getName()+": "+stick --);
        }
    }
}
新建线程1: 20
新建线程2: 19
新建线程2: 17
新建线程2: 16
新建线程2: 15
新建线程2: 13
新建线程2: 12
新建线程2: 11
新建线程2: 10
新建线程2: 9
新建线程2: 8
新建线程2: 7
新建线程1: 18
新建线程1: 5
新建线程1: 4
新建线程1: 3
新建线程1: 2
新建线程1: 1
新建线程2: 6
新建线程3: 14

(3)实现Callable方法,结合线程池使用。 

class CallableTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //提交任务,并用 Future提交返回结果
        Future<Integer> future = service.submit(new CallableTask());
        final Integer integer = future.get();
        System.out.println(integer);
    }
}

Java线程池_Seventeen117的博客-CSDN博客线程池使用及部分源码https://blog.csdn.net/Soil_Three_66/article/details/127303211?spm=1001.2014.3001.5502 

(4)使用lambda

new Thread(() ‐ > System.out.println(Thread.currentThread().getName())).start();

3.线程生命周期

(1)JAVA线程状态

public enum State {
        /**
         * Thread state for a thread which has not yet started.
         * 新建状态:new出了一个新线程但是还没有开始的状态
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         * 可执行状态(就绪状态):当调⽤线程对象的start()⽅法( t.start(); ),线程即进⼊就绪状态。
         * 处于就绪状态的线程,只是说 明此线程已经做好了准备,随时等待CPU调度执⾏,
         * 并不是说执⾏了t.start()此线程⽴即就会执⾏;
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         * 阻塞状态:
         * 等待监视器锁而阻塞的线程的线程状态。处于阻塞状态的线程正在等待监视锁进入同步块/方法,
         * 或者在调用Object.wait后重新进入同步块/方法。
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         * 等待状态:
         * 调用了wait() ,无参join(),LockSupport.park会进入等待状态
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         * 定时等待状态:调用了sleep,wait(long),join(long),LockSupport.parkNanos,LockSupport.parkUntil会进入定时等待状态
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         * 终止状态:线程已完成运行
         */
        TERMINATED;
    }
  • NEW:新建状态,new出了一个新线程但是还没有开始的状态。
  • RUNNABLE:可执行状态(就绪状态),当调⽤线程对象的start()⽅法( t.start(); ),线程即进⼊就绪状态。处于就绪状态的线程,只是说 明此线程已经做好了准备,随时等待CPU调度执⾏,并不是说执⾏了t.start()此线程⽴即就会执⾏。
  • BLOCKED:阻塞状态,等待监视器锁而阻塞的线程的线程状态。处于阻塞状态的线程正在等待监视锁进入同步块/方法,或者在调用Object.wait后重新进入同步块/方法。
  • WAITING:等待状态,调用了wait() ,无参join(),LockSupport.park会进入等待状态。
  • TIMED_WAITING:定时等待状态,调用了sleep,wait(long),join(long),LockSupport.parkNanos,LockSupport.parkUntil会进入定时等待状态。
  • TERMINATED:终止状态,线程已完成运行。

(2)操作系统线程状态

  1. 初始状态指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层面,真正的线程还没有创建。
  2. 可运行状态,指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
  3. 当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程,被分配到CPU 的线程的状态就转换成了运行状态
  4. 运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。
  5. 线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。

4.线程常用方法

方法名说明
public static void sleep(long millis)
当前线程主动休眠 millis 毫秒。
public static void yield()
当前线程主动放弃时间⽚,回到就绪状态,竞争 下⼀次时间⽚。
public final void join()
允许其他线程加⼊到当前线程中。
public void setPriority(int)
线程优先级为1-10,默认为5,优先级越⾼,表示 获取CPU机会越多。
public void setDaemon(boolean)
设置为守护线程线程有两类:⽤户线程(前台线程)、守护线程(后台线程)
public final void stop()强制终止线程

(1)线程休眠

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 3; i > 0; i--) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("倒计时:"+i);
        }
    }
}

 (2)线程让步

class Runnable1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}

class Runnable2 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}

public class Test {

    public static void main(String[] args) {
        new Thread(new Runnable1()).start();
        new Thread(new Runnable2()).start();
    }
}

没有让步的结果 

Thread-1: 0
Thread-1: 1
Thread-1: 2
Thread-1: 3
Thread-1: 4
Thread-1: 5
Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-1: 6
Thread-1: 7
Thread-1: 8
Thread-1: 9
Thread-0: 9
Thread-0: 10
Thread-0: 11
Thread-0: 12
Thread-0: 13
Thread-0: 14
Thread-0: 15
Thread-0: 16
Thread-0: 17
Thread-0: 18
Thread-0: 19

class Runnable1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("A: "+i);
        }
    }
}

class Runnable2 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // 线程让步
            Thread.yield();
            System.out.println("B: "+i);
        }
    }
}

public class Test {

    public static void main(String[] args) {
        new Thread(new Runnable1()).start();
        new Thread(new Runnable2()).start();
    }
}

让步后的结果

A: 0
A: 1
A: 2
B: 0
A: 3
A: 4
A: 5
B: 1
B: 2
B: 3
B: 4
B: 5
A: 6
B: 6
B: 7
B: 8
A: 7
B: 9
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19

让步后的结果也没有出现线程A的结果都在B的前面

结论:

  • Thread.yield() ⽅法作⽤是:暂停当前正在执⾏的线程对象(及放弃当前拥有的cup资源),并执⾏其他线程。
  • yield() 做的是让当前运⾏线程回到可运⾏状态,以允许具有相同优先级的其他线程获得运⾏机会。因此,使⽤ yield() 的⽬的是让相同优先级的线程之间能适当的轮转执⾏。但是,实际中⽆法保证 yield() 达到让步⽬的,因为让步的线程还有可能被线程调度程序再次选中。

sleep()和yield()的区别

  • sleep()使当前线程进⼊停滞状态(不可运行状态),所以执⾏sleep()的线程在指定的时间内肯定不会被执⾏;
  • yield()只是使当前线程重新回到可执⾏状态,所以执⾏yield()的线程有可能在进⼊到可执⾏状态⻢上⼜被执⾏。

(3)线程的合并

Thread 中, join() ⽅法的作⽤是调⽤线程等待该线程完成后,才能继续往下运⾏。
class Runnable1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("A: "+i);
        }
    }
}


public class Test {

    public static void main(String[] args) {
        System.out.println("主线程运行开始");
        new Thread(new Runnable1()).start();
        System.out.println("主线程运行结束");
    }
}

子线程没有合并到主线程结果为:

主线程运行开始
主线程运行结束
A: 0
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19

class Runnable1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("A: "+i);
        }
    }
}


public class Test {

    public static void main(String[] args) {
        System.out.println("主线程运行开始");
        try {
            Thread thread = new Thread(new Runnable1());
            thread.start();
            // 合并到主线程
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程运行结束");
    }
}
主线程运行开始
A: 0
A: 1
A: 2
A: 3
A: 4
A: 5
A: 6
A: 7
A: 8
A: 9
A: 10
A: 11
A: 12
A: 13
A: 14
A: 15
A: 16
A: 17
A: 18
A: 19
主线程运行结束

(5)设置优先级:Java线程的优先级⽤整数表示,取值范围是1~10,有以下三个静态常量

  • static int MAX_PRIORITY 线程可以具有的最⾼优先级,取值为10
  • static int MIN_PRIORITY: 线程可以具有的最低优先级,取值为1
  • static int NORM_PRIORITY: 分配给线程的默认优先级,取值为5
class Runnable1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}


public class Test {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable1());
        thread1.setName("线程A");
        Thread thread2 = new Thread(new Runnable1());
        thread2.setName("线程B");
        Thread thread3 = new Thread(new Runnable1());
        thread3.setName("线程C");

        thread1.setPriority(Thread.MAX_PRIORITY);
        thread3.setPriority(Thread.MIN_PRIORITY);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}
线程B: 0
线程B: 1
线程B: 2
线程B: 3
线程B: 4
线程C: 0
线程A: 0
线程A: 1
线程A: 2
线程A: 3
线程A: 4
线程C: 1
线程C: 2
线程C: 3
线程C: 4

设置线程A的优先级为10,B为默认的5,C为1,执行结果并没有按照A-B-C的顺序来。

调整线程优先级:Java线程有优先级,优先级⾼的线程会获得较多的运⾏机会。优先级 : 只能反映 线程的紧急程度 , 不能决定是否⼀定先执⾏。

(6)守护线程:如果程序中所有前台线程都执⾏完毕了,后台线程会⾃动结束。setDaemon(true)设置守护线程。

线程有两类:⽤户线程(前台线程)、守护线程(后台线程)

class Runnable1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+": "+i);
        }
    }
}


public class Test {

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable1());
        thread1.setName("守护线程A");
        // 设置thread1为守护线程
        thread1.setDaemon(true);
        thread1.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程:"+i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
主线程:0
主线程:1
主线程:2
守护线程A: 0
主线程:3
主线程:4
守护线程A: 1
主线程:5
主线程:6
主线程:7
守护线程A: 2
主线程:8
主线程:9
守护线程A: 3

 结果:当主线程结束时,守护线程A虽然没有执行完,但是也结束了。

(7)stop()

public class ThreadStopDemo {
    private static Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("A:等待获取锁");
            synchronized (lock) {
                System.out.println("A:获取锁开始执行");
                try {
                    Thread.sleep(60000);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
            }
            System.out.println("A:执行完成");

        });
        thread.start();
        Thread.sleep(2000);
        // 停止thread,并释放锁
        thread.stop();
        new Thread(() -> {
            System.out.println("B:等待获取锁");
            synchronized (lock) {
                System.out.println("B:获取锁开始执行");
            }
            System.out.println("B:执行完成");
        }).start();

    }
}
A:等待获取锁
A:获取锁开始执行
B:等待获取锁
B:获取锁开始执行
B:执行完成

 如果通过stop执行线程终止,A线程还没有执行完,强制终止,这样会造成数据不一致,业务逻辑不完整,Java提供了另一种线程中断机制。

Java线程的中断机制:Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。被 中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选 择压根不停止。

  • interrupt():将线程的中断标志位设置为true,不会停止线程
  • isInterrupted():判断当前线程的中断标志位是否为true,不会清除中断标志位
  • Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为fasle
public class ThreadStopDemo {
    static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while(!Thread.currentThread().isInterrupted() && i<1000){
                i++;
                System.out.println("轮训任务:"+i);
            }
            System.out.println("线程结束!");
        });
        thread.start();
        // 主线程休眠1毫秒后增中断加标记位
        Thread.sleep(1);
        // 中断标记
        //thread.interrupt();
    }
}

 

 不加中断标记线程正常执行,进行1000次轮训


加上中断标记后线程运行到126(随机的数,看cup执行效率),检测到标记位为true,程序停止运行,这个标记位是被中断线程自己可控的。

public class ThreadStopDemo {
    static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            Thread.interrupted();
            while(!Thread.currentThread().isInterrupted() && i<1000){
                i++;
                System.out.println("轮训任务:"+i);
            }
            System.out.println("线程结束!");
        });
        thread.start();
        // 主线程休眠500毫秒后增中断加标记位
        Thread.sleep(1);
        thread.interrupt();
    }
}

使用 Thread.interrupted()进行判断会清除标记位

 sleep()方法也会清除标记位

public class ThreadStopDemo {
    static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {

            while (!Thread.currentThread().isInterrupted() && i < 10) {
                System.out.println("轮训任务:" + i++);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // Thread.currentThread().interrupt();
                }
            }
            System.out.println("线程结束!");
        });
        thread.start();
        // 主线程休眠500毫秒后增中断加标记位
        Thread.sleep(500);
        thread.interrupt();
    }
}

针对sleep的情况,要在cache异常中重新设置标记位

public class ThreadStopDemo {
    static int i = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {

            while (!Thread.currentThread().isInterrupted() && i < 10) {
                System.out.println("轮训任务:" + i++);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("线程结束!");
        });
        thread.start();
        // 主线程休眠500毫秒后增中断加标记位
        Thread.sleep(500);
        thread.interrupt();
    }
}

 sleep和wait方法都会导致标记位失效,sleep报异常sleep interrupted,wait报异常InterruptedException

5.线程安全问题

为什么会出现线程安全问题?

  • 当多线程并发访问临界资源时,如果破坏原⼦操作,可能会造成数据不⼀致。
  • 临界资源:共享资源(同⼀对象),⼀次仅允许⼀个线程使⽤,才可保证其正确性。
  • 原⼦操作:不可分割的多步操作,被视作⼀个整体,其顺序和步骤不可打乱或缺省。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考虑线程同步,否则的话就可能影响线程安全。

class TicketRunnable implements Runnable {
    private int ticket = 100;
    //每个窗⼝卖票的操作 窗口永远开启
    @Override
    public void run() {
        while (true) {//有票可以卖
             //出票操作
            if (ticket > 0) {
                //使⽤sleep模拟⼀下出票时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
            }
        }
    }
}

class ThreadSafe {
    public static void main(String[] args) throws Exception {
        TicketRunnable t = new TicketRunnable();
        Thread t1 = new Thread(t, "窗⼝1");
        Thread t2 = new Thread(t, "窗⼝2");
        Thread t3 = new Thread(t, "窗⼝3");
        //3个窗⼝同时卖票
        t1.start();
        t2.start();
        t3.start();
    }
}

结果为会出现不同的窗口卖不同的票,还会出现第0张的情况,这就是线程中使用全局变量,导致参数共享,在执行阶段没有做同步处理,导致线程不安全。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。有三种方式完成同步操作:

  • 同步代码块
  • 同步方法
  • 锁机制

同步锁:对象的同步锁只是⼀个概念,可以想象为在对象上标记了⼀个锁。

  • 锁对象 可以是任意类型。
  • 多个线程对象 要使⽤同⼀把锁。

在任何时候,最多允许⼀个线程拥有同步锁,谁拿到锁就进⼊代码块,其他的线程只能在外等着(BLOCKED)。

(1)同步代码块

语法:

synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原⼦操作)
}
class TicketRunnable implements Runnable {
    private int ticket = 20;
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {//有票可以卖
             //出票操作
            synchronized (lock){
                if (ticket > 0) {
                    //使⽤sleep模拟⼀下出票时间
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
                }
            }
        }
    }
}

class ThreadSafe {
    public static void main(String[] args) throws Exception {
        TicketRunnable t = new TicketRunnable();
        Thread t1 = new Thread(t, "窗⼝1");
        Thread t2 = new Thread(t, "窗⼝2");
        Thread t3 = new Thread(t, "窗⼝3");
        //3个窗⼝同时卖票
        t1.start();
        t2.start();
        t3.start();
    }
}
窗⼝2正在卖票:20
窗⼝2正在卖票:19
窗⼝2正在卖票:18
窗⼝2正在卖票:17
窗⼝2正在卖票:16
窗⼝2正在卖票:15
窗⼝2正在卖票:14
窗⼝2正在卖票:13
窗⼝2正在卖票:12
窗⼝2正在卖票:11
窗⼝2正在卖票:10
窗⼝2正在卖票:9
窗⼝2正在卖票:8
窗⼝2正在卖票:7
窗⼝2正在卖票:6
窗⼝2正在卖票:5
窗⼝2正在卖票:4
窗⼝2正在卖票:3
窗⼝2正在卖票:2
窗⼝2正在卖票:1

执行结果没有超卖现象,没有重复卖现象。

(2)同步方法:使⽤synchronized修饰的⽅法,就叫做同步⽅法,保证A线程执⾏该⽅法的时候,其他线程只能在⽅法外等着。

语法:

synchronized 返回值类型 ⽅法名称(形参列表){ 
    //对当前对象(this)加锁 // 代码(原⼦操作) 
}
  • 只有拥有对象互斥锁标记的线程,才能进⼊该对象加锁的同步⽅法中。
  • 线程退出同步⽅法时,会释放相应的互斥锁标记。
  • 对于⾮static⽅法,同步锁就是this。
  • 如果⽅式是静态,锁是类名.class。
class TicketRunnable implements Runnable {
    private int ticket = 20;
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {//有票可以卖
            sellTicket();
            if(ticket<=0){
                break;
            }
        }
    }
    /**
     * 锁对象,谁调⽤这个⽅法,就是谁
     * 隐含锁对象,就是this 
     * 静态⽅法,隐含锁对象就是TicketRunnable.class
     */
    public synchronized void sellTicket(){
        if (ticket > 0) {
            //使⽤sleep模拟⼀下出票时间
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖票:" + ticket--);
        }
    }
}

class ThreadSafe {
    public static void main(String[] args) throws Exception {
        TicketRunnable t = new TicketRunnable();
        Thread t1 = new Thread(t, "窗⼝1");
        Thread t2 = new Thread(t, "窗⼝2");
        Thread t3 = new Thread(t, "窗⼝3");
        //3个窗⼝同时卖票
        t1.start();
        t2.start();
        t3.start();
    }
}

(3)Lock

  • JDK5加⼊,与synchronized⽐较,显示定义,结构更灵活。
  • 提供更多实⽤性⽅法,功能更强⼤、性能更优越。
方法名描述
void lock()
获取锁,如锁被占⽤,则等待。
boolean tryLock()
尝试获取锁(成功返回true。失败返回false,不 阻塞)。
void unlock()
释放锁。

ReentrantLock: 重入锁,Lock接⼝的实现类,与synchronized⼀样具有互斥锁功能。

public class MyList {
    //创建锁
    private Lock lock = new ReentrantLock();
    private String[] str = {"A", "B", "", "", ""};
    private int count = 2;

    public void add(String value) {
        //当没有锁的时候,会出现覆盖的情况
        str[count] = value;
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
        System.out.println(Thread.currentThread().getName() + "添加了" + value);
    }

    public String[] getStr() {
        return str;
    }

    public static void main(String[] args) throws InterruptedException {
        MyList myList = new MyList();
        Thread t1 = new Thread(() -> myList.add("hello"));
        t1.start();
        Thread t2 = new Thread(() -> myList.add("world"));
        t2.start();
        t1.join();
        t2.join();
        String[] str = myList.getStr();
        for (String s : str) {
            System.out.println("s:" + s);
        }
    }
}
运行结果:
Thread-0添加了hello
Thread-1添加了world
s:A
s:B
s:world
s:
s:

如果不加锁会出现覆盖,结果应该是[A,B,hello,world,""],此时线程1的操作覆盖了线程0的操作,所以结果为:[A,B,world,"",""]。下面是加锁的情况

public class MyList {
    //创建锁
    private Lock lock = new ReentrantLock();
    private String[] str = {"A", "B", "", "", ""};
    private int count = 2;

    public void add(String value) {
        lock.lock();
        try {
            str[count] = value;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count++;
            System.out.println(Thread.currentThread().getName() + "添加 了" + value);
        } finally {
            lock.unlock();
        }
    }

    public String[] getStr() {
        return str;
    }

    public static void main(String[] args) throws InterruptedException {
        MyList myList = new MyList();
        Thread t1 = new Thread(() -> myList.add("hello"));
        t1.start();
        Thread t2 = new Thread(() -> myList.add("world"));
        t2.start();
        t1.join();
        t2.join();
        String[] str = myList.getStr();
        for (String s : str) {
            System.out.println("s:" + s);
        }
    }
}
运行结果:
Thread-0添加 了hello
Thread-1添加 了world
s:A
s:B
s:hello
s:world
s:

6.线程间的通讯

1.volatile

volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。

public class VolatileDemo {
    private static volatile boolean flag = true;

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (flag) {
                    System.out.println("trun on");
                    flag = false;
                }
            }
        }).start();
        new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (!flag) {
                    System.out.println("trun off");
                    flag = true;
                }
            }
        }).start();
    }
}

线程1和线程2之间可以共享flag变量,做到相互通信一直循环输出。如果不使用volatile会造成死锁

public class VolatileDemo {
    private static boolean flag = true;

    public static void main(String[] args) {
        new Thread(() -> {
            while (true) {

                if (flag) {
                    System.out.println("trun on");
                    flag = false;
                }
            }
        }).start();
        new Thread(() -> {
            while (true) {

                if (!flag) {
                    System.out.println("trun off");
                    flag = true;
                }
            }
        }).start();
    }
}

 等待唤醒(等待通知)机制

等待唤醒机制可以基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法,线程将进入等待队列进行等待直到被唤醒。

public class WaitDemo {
    private static Object lock = new Object();
    private static boolean flag = true;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (lock) {
                while (flag) {
                    try {
                        System.out.println("wait start .......");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("wait end ....... ");
            }
        }).start();
        new Thread(() -> {
            if (flag) {
                synchronized (lock) {
                    if (flag) {
                        lock.notify();
                        System.out.println("notify .......");
                        flag = false;
                    }
                }
            }
        }).start();
    }
}

LockSupport:是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待“许可”,调用unpark则为指定线程提供“许可”。使用它可以在任何场合使线程阻塞,可以指定任何线程进行唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。

public class LockSupportTest {

    public static void main(String[] args) {
        Thread parkThread = new Thread(new ParkThread());
        parkThread.start();
        System.out.println("唤醒parkThread");
        LockSupport.unpark(parkThread);
    }

    static class ParkThread implements Runnable {
        @Override
        public void run() {
            System.out.println("ParkThread开始执行");
            LockSupport.park();
            System.out.println("ParkThread执行完成");
        }
    }
}

 管道输入输出流管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。

public class Piped {

    public static void main(String[] args) throws Exception {
        PipedWriter out = new PipedWriter();
        PipedReader in = new PipedReader();
        // 将输出流和输入流进行连接,否则在使用时会抛出IOException
        out.connect(in);
        Thread printThread = new Thread(new Print(in), "PrintThread");
        printThread.start();
        int receive = 0;
        try {
            while ((receive = System.in.read()) != -1){
                out.write(receive);
            }
        } finally {
            out.close();

        }
    }

    static class Print implements Runnable {
        private PipedReader in;

        public Print(PipedReader in) {
            this.in = in;

        }

        @Override

        public void run() {
            int receive = 0;
            try {
                while ((receive = in.read()) != -1){
                    System.out.print((char) receive);
                }
            } catch (IOException ex) {

            }
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Seventeen117

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值