多线程(线程的创建以及常用方法)

多线程基础

一、基本概念

进程是执行程序的依次执行过程,它是一个动态的概念。是系统资源分配的单位。

一个进程中可以包含多个线程。线程是CPU调度和执行的单位。

多线程是指有多个CPU,即多核。

二、线程的创建

常见线程有三种方式:继承Thread类、实现Runnable接口、实现Callable接口

1、继承Thread类

步骤:

  1. 自定义线程类继承Thread类
  2. 重写run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
public class TestThread1 extends Thread{
    @Override
    public void run() {
        // run方法线程体
        for (int i = 0; i < 20 ; i++) {
            System.out.println(Thread.currentThread().getName() +  "---hello");
        }
    }

    public static void main(String[] args) {
        // main 线程,主线程

        // 创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        // 调用start方法,开启线程
        testThread1.start();

        for (int i = 0; i < 200 ; i++) {
            System.out.println(Thread.currentThread().getName() + "---Word---"+i);
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hjuSWDKg-1585149036172)(C:\Users\惠秋丽\AppData\Roaming\Typora\typora-user-images\1584670531144.png)]

注意:线程开启不一定立即执行,有CPU调度执行

2、实现Runnable接口

步骤:

  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
public class TestThread2 implements Runnable{

    @Override
    public void run() {
        // run方法线程体
        for (int i = 0; i < 20 ; i++) {
            System.out.println(Thread.currentThread().getName() +  "---hello");
        }
    }

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        TestThread2 testThread2 = new TestThread2();
        // 创建线程对象,通过线程对象来开启我们的线程,代理
        new Thread(testThread2).start();
        
        for (int i = 0; i < 200 ; i++) {
            System.out.println(Thread.currentThread().getName() + "---Word---"+i);
        }
    }
}

考虑一个问题,既然有了继承Thread类来创建线程,为什么会还会有实现Runnable接口?

  1. 可以解决继承Thread类后不能在继承其他的类的单继承问题。
  2. Runnable实现的多线程的程序类可以更更好的描述出程序共享的概念 。

3、实现Callable接口

步骤:

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get();
  7. 关闭服务:ser.shutdownNow();
public class TestThread4 {

    private static void test1() throws ExecutionException, InterruptedException {
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName()+":call");
                return "call finish";
            }
        };

        ExecutorService pool = Executors.newFixedThreadPool(1);
        Future future = pool.submit(callable);

        System.out.println(Thread.currentThread().getName()+ ": pool submit ,before get()" );
        System.out.println(future.get());
        pool.shutdownNow();
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
    }
}

思考:有了实现Runnable接口已经解决了单继承问题,为神马还会有实现Callable接口?

Callable的好处:

  • 可以定义返回值
  • 可以抛出异常

三、多线程的方法

1、线程休眠(sleep方法)

  • 线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后在恢复执行。

  • 注意:线程休眠会交出CPU,让CPU去执行其他的任务,但是有一点非常重要,sleep方法不会释放锁,也就是说,如果当前线程持有某个对象的锁,则即使调用的sleep方法,其他的线程也无法获得这个对象。

  • 休眠时间使用毫秒作为单位

public class ThreadSleep {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000 ; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println("当前线程: " +Thread.currentThread().getName()+" ,i = " +i);
        }
    }
}

2、线程让步(yield方法)

  • 暂停当前正在执行的线程对象,并执行其他线程。

    意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样 不会释放锁。但是yield不不能控制具体的交出CPU的时间,另外, yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

  • 注意:

    调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的 。

  • 线程礼让不一定成功,看CPU的心情。

public class ThreadYieldTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        new Thread(mt).start();
        new Thread(mt).start();
        new Thread(mt).start();
    }

}
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 3 ; i++) {
            Thread.yield();
            System.out.println("当前线程: " +
                    Thread.currentThread().getName()+" ,i = " +i);
        }
    }
}

3、join方法

等待该线程终止。意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后,再开始执行主线程。

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadJoinTest {

    public static void main(String[] args) throws InterruptedException {
        MyThread mt = new MyThread();
        Thread thread = new Thread(mt,"子线程A");
        thread.start();
        System.out.println(Thread.currentThread().getName());
        thread.join();
        System.out.println("代码结束");
    }
    public static void printTime() {
        Date date=new Date();
        DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time=format.format(date);
        System.out.println(time);
    }
}
class MyThread implements Runnable {

    @Override
    public void run() {
        try {
            System.out.println("主线程休眠前的时间");
            ThreadJoinTest.printTime();
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName());
            System.out.println("睡眠结束的时间");
            ThreadJoinTest.printTime();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4、线程停止

多线程中线程停止有三种方式:

  1. 设置标志位,可以使线程正常退出!
  2. 使用stop方法强制使线程退出,但是这个方法不太安全已经被抛弃了!
  3. 使用Thread类中的interrupt()可以中断线程!

4.1、设置标志位使线程正常退出

public class ThreadExist1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread,"子线程A");
        thread1.start();
        Thread.sleep(2000);
        myThread.setFlag(false);
        System.out.println("代码结束");
    }
}
class MyThread implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int i = 1;
        while (flag) {
            try {
                Thread.sleep(1000);
                System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
                i++;
            } catch (InterruptedException e) {
// TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

4.2、使用stop方法退出线程

public class ThreadExist1 {
    public static void main(String[] args) throws InterruptedException {
        MyThread myThread = new MyThread();
        Thread thread1 = new Thread(myThread,"子线程A");
        thread1.start();
        Thread.sleep(3000);
        thread1.stop();
        System.out.println("代码结束");
    }
}

使用stop方法强制使线程退出,但是该方法不太安全所以已经被弃了。

4.3、interrupt()中断线程

interrupt()方法:不会真正结束线程,再当前线程中打上一个停止的标记。

Thread类中的interrupted()方法:测试当前线程是否中断!该方法有检测中断并清除中断状态的作用

isInterrputed()方法:测试线程是否已经中断!

public class ThreadinterruptTest {
    public static void main(String[] args) {
        Thread thread = new ThreadinterruptTestThread();
        thread.start();
        thread.interrupt();
        System.out.println("是否停止?1" + thread.isInterrupted());
        System.out.println("是否停止?2" + Thread.interrupted());
		
    }
}
class ThreadinterruptTestThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 1000 ; i++) {
            System.out.println("i+ " + i);
        }
    }
}

运行结果:

threadtest.ThreadinterruptTest
i+ 0
是否停止?1true
i+ 1
i+ 2
i+ 3
i+ 4
是否停止?2false
i+ 5
...

thread.isInterrupted()方法:是检查ThreadinterruptTestThread是否被打上了停止的标记,

Thread.interrupted()方法:是检查主线程是否被打上了停止标记!


    public static void main(String[] args) {
        Thread.currentThread().interrupt();
        System.out.println("是否停止?1" + Thread.interrupted());
        System.out.println("是否停止?2" + Thread.interrupted());
    }

运行结果:

是否停止?1true
是否停止?2false

测试当前线程是否已经中断,线程的中断状态由方法清除。如果两次连续调用,第二次调用将返货false!

interrupted()方法有检测中断并清除中断状态的作用


public class ThreadinterruptTest2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new ThreadinterruptTest2Thread();
        t.start();
        t.interrupt();
    }
}
class ThreadinterruptTest2Thread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < 10000; i++) {
                if (this.isInterrupted()) {
                    System.out.println("已经是停止状态了!我有与退出");
                    throw new Exception();
                }
                System.out.println("i : " +i);
            }
            System.out.println("这里是结束循环后的代码");
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cOL81gDM-1585149036174)(C:\Users\惠秋丽\AppData\Roaming\Typora\typora-user-images\1585052726555.png)]

public class ThreadinterruptTest3 {
    public static void main(String[] args) {
        Thread t = new ThreadinterruptTest3Thread();
        t.start();
        t.interrupt();
    }
}
class ThreadinterruptTest3Thread extends Thread {
    @Override
    public void run() {
        try {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                String s = new String();
            }
            System.out.println("开始线程");
            Thread.sleep(20000);
            System.out.println("结束线程");
        }catch (Exception e) {
            System.out.println("进入异常代码块");
            e.printStackTrace();
        }
    }
}


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-laevGM9N-1585149036175)(C:\Users\惠秋丽\AppData\Roaming\Typora\typora-user-images\1585053073647.png)]

如果程序中有sleep代码,不管是否进入到sleep的状态,如果调用了interrupt方法都会长生异常信息!

总结

  • interrupt()是给线程设置中断标志

  • interrupted()是检测当前线程是否中断 并 清除中断状态 ;

  • isInterrupted()只检测中断

注意:

interrupted()作用于当前线程 !

interrupt()和isInterrupted()作用于此线程 , 即代码中调用此方法的实例所代表的线程 !

4.5、守护线程

守护线程是一种特殊的线程,它属于是一种陪伴线程。简单点说 java 中有两种线程:用户线程和守护
线程。可以通过 isDaemon() 方法来区别它们:如果返回 false ,则说明该线程是“用户线程”;否则就是“守护线程”。典型的守护线程就是垃圾回收线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后一个非守护线程结束时,守护线程才会随着JVM一同停止工作。
注意:主线程main是用户线程。

public class ThreadDaeemonTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new A(),"子线程A");
        // 设置线程A为守护线程,此语句句必须在start⽅方法之前执⾏行行
        thread1.setDaemon(true);
        thread1.start();
        Thread thread2 = new Thread(new A(),"子线程B");
        thread2.start();
        Thread.sleep(3000);
        // 中断⾮非守护线程
        thread2.interrupt();
        Thread.sleep(10000);
        System.out.println("代码结束");
    }
}
class A implements Runnable{
    private int i;
    @Override
    public void run() {
        try {
            while (true) {
                i ++ ;
                System.out.println("线程名称: " + Thread.currentThread().getName() + ",i=" + i
                        + ",是否为守护线程:" + Thread.currentThread().isDaemon());
                Thread.sleep(1000);
            }
        }catch (Exception e) {
            System.out.println("线程名称: " +
                    Thread.currentThread().getName() + "中断线程了了");
        }
    }
}

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cHlaSYan-1585149036176)(C:\Users\惠秋丽\AppData\Roaming\Typora\typora-user-images\1585057146538.png)]

从上面结果可以看出来, B是用户线程当它中断了之后,守护线程还没有结束,是因为主线程(用户线
程)还没有结束,所以说明是所有的用户线程结束之后守护线程才会结束。

四、线程的同步问题

1、同步问题的引出

需求:多个线程同时卖票

public class TestDemo {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        new Thread(t, "黄牛A").start();
        new Thread(t, "黄牛B").start();
        new Thread(t, "黄牛C").start();
    }
}
class MyThread implements Runnable{

    private int tickets = 10;
    @Override
    public void run() {
        while (tickets > 0) {
            try {
                Thread.sleep(200);
            }catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+",还有"+tickets--+"张票");
        }
    }
}

运行上面的代码,我们发现出现了问题,竟然有的票被卖了多次,还卖出了负票,这显然是不合理的,我们把这个称之为不同步操作

那么怎么解决呢?

2、同步处理

所谓的同步指的是所有的线程不是一起进入到方法中执行,而是按照顺序一个一个进来。

synchronized处理理同步问题

如果要想实现这把"锁"的功能,可以采用关键字synchronized来处理!

使用synchronized关键字处理理有两种模式:同步代码块、同步方法

  • 使用同步代码块 : 如果要使用同步代码块必须设置一个要锁定的对象,所以一般可以锁定当
    前对象:this

代码实现:

public class TestDemo {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        new Thread(t, "黄牛A").start();
        new Thread(t, "黄牛B").start();
        new Thread(t, "黄牛C").start();
    }
}

class MyThread implements Runnable{

    private int tickets = 10;
    @Override
    public void run() {
        synchronized (this) {
            while (tickets > 0) {
                try {
                    Thread.sleep(200);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ",还有" + tickets-- + "张票");
            }
        }
    }
}


第一种方式是在方法里拦截的,也就是说进入到方法中的线程依然可能会有多个。

  • 同步方法:
public class TestDemo {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        new Thread(t, "黄牛A").start();
        new Thread(t, "黄牛B").start();
        new Thread(t, "黄牛C").start();
    }
}

class MyThread implements Runnable{

    private int tickets = 10;
    @Override
    public void run() {
        sale();
    }

    private synchronized  void sale() {

        while (tickets > 0) {
            try {
                Thread.sleep(200);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ",还有" + tickets-- + "张票");
        }
    }
}

同步虽然可以保证数据的完整性(线程安全操作),但是其执行的速度会很慢。

3、关于synchronized的额外说明

先来看一段代码:

范例:观察synchronized锁多对象

public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 3 ; i++) {
            Thread thread = new MyThread() ;
            thread.start();
        }
    }
}
class Sync {
    public synchronized void test() {
        System.out.println("test⽅方法开始,当前线程为 "+Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("test⽅方法结束,当前线程为 "+Thread.currentThread().getName());
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        Sync sync = new Sync() ;
        sync.test();
    }
}

通过上述代码以及运行结果我们可以发现,没有看到synchronized起到作用,三个线程同时运行test()
方法。

实际上, synchronized(this)以及非static的synchronized方法,只能防止多个线程同时执行同一个
对象的同步代码段。即synchronized锁住的是括号里的对象,而不不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。

当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行行完成释放锁,才能再次给对象加锁,这样才达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行。

那么,如果真要锁住这段代码,要怎么做?

介绍两种思路

  • 第一种也是最容易易想到的,只要锁住同一个对象不不就OK了了?

  • 第二种思路也是我们常用的思路,让synchronized锁这个类对应的Class对象

下面分别介绍这两种方法:

3.1、锁住同一个对象

修改上述代码,锁同一个对象:

class Sync {
    public void test() {
        synchronized (this) {
            System.out.println("test⽅方法开始,当前线程为 " +
                    Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("test⽅方法结束,当前线程为 " +
                    Thread.currentThread().getName());
        }
    }
}
class MyThread extends Thread {
    private Sync sync ;
    public MyThread(Sync sync) {
        this.sync = sync ;
    }
    @Override
    public void run() {
        this.sync.test();
    }
}
public class Test {
    public static void main(String[] args) {
        Sync sync = new Sync() ;
        for (int i = 0; i < 3 ; i++) {
            Thread thread = new MyThread(sync) ;
            thread.start();
        }
    }
}

3.2、synchronized锁这个类对应的Class对象

class Sync {
    public void test() {
        synchronized(Sync.class) {
            System.out.println("test⽅方法开始,当前线程为 " +
                               Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("test⽅方法结束,当前线程为 " +
                               Thread.currentThread().getName());
        }
    }
}
class MyThread extends Thread {
    @Override
    public void run() {Sync sync = new Sync() ;
                       sync.test();
                      }
}
public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 3 ; i++) {
            Thread thread = new MyThread() ;
            thread.start();
        }
    }
}

上面代码用synchronized(Sync.class)实现了了全局锁的效果。因此,如果要想锁的是代码段,锁住多个对象的同一方法,使用这种全局锁,锁的是类而不不是this。

static synchronized方法, static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是
this,而是类的Class对象,所以, static synchronized方法也相当于全局锁,相当于锁住了代码段。

发布了58 篇原创文章 · 获赞 34 · 访问量 3740
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览