栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java多线程

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java多线程

1.继承Thread类创建多线程
public class ThreadTest {
    public static void main(String[] args) {
        //创建两个线程并启动
        //start方法的两个作用:1.启动该线程,2.调用run方法,所以启动一个线程是调用start方法,调用run方法不会启动线程。
        new MyThread1().start();
        new MyThread1().start();
    }
}
class MyThread1 extends Thread{
    //输出100以内的偶数
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

执行结果(部分截图):

2.实现Runnable接口创建多线程
//实现Runnable接口创建多线程
public class RunnableTest {
    public static void main(String[] args) {

        MyThread myThread = new MyThread();

        new Thread(myThread).start();
        new Thread(myThread).start();
    }

}
class MyThread implements Runnable{

    //输出100以内的偶数
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i%2==0){
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}

执行结果(部分截图):

Thread类部分源码:

public class Thread implements Runnable  {

    //这里的target通过构造方法初始化
    private Runnable target;

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

}

实现runnable方法说明:通过构造方法将Runnable接口的实现类赋值给target,调用start方法会调用Thread类中的run方法,判断target是否为空,不为空,则调用target的run方法。

3.两种方式的对比:

        优先选择实现runnable接口的方式,原因:

1.继承的方式受到类的单继承的限制,继承了Thread类就不能有其他父类,实现接口的方式则不会有这个问题。

2.实现的方式更适合来处理多个线程共享数据的情况。代码举例:

需求:创建两个线程对应两个买票窗口共同来卖100张票

继承方式代码:

public class ThreadTest2 {
    public static void main(String[] args) {
        new MyThread2("窗口1").start();
        new MyThread2("窗口2").start();
    }
}
class MyThread2 extends Thread{

    public MyThread2(String name){
        super(name);
    }

    //票数模拟买票
    //private int ticket=100;
    //这里必须使用static,否则ticket将会是一个线程有一份。而不是共享数据
    private static int ticket=100;

    @Override
    public void run() {
        while(true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"出售第"+ticket+"张票");
                ticket--;
            }else{
                break;
            }
        }
    }
}

实现runnable方式代码:

package demo01;

public class RunnableTest2 {
    public static void main(String[] args) {
        MyThread3 myThread3 = new MyThread3();
        //两个线程共用一个MyThread3对象
        new Thread(myThread3).start();
        new Thread(myThread3).start();
    }
}
class MyThread3 implements Runnable{

    //这里不需要加static
    private  int ticket=100;

    @Override
    public void run() {
        while(true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"出售第"+ticket+"张票");
                ticket--;
            }else{
                break;
            }
        }
    }
}

所以实现的方式更适合多个线程共享数据的情况。 

4.Thread类的常用方法

5.线程的优先级 

6.守护线程

      守护线程依赖于用户线程(默认就是用户线程),当jvm中只剩下守护线程时,当前jvm将退出,jvm中的垃圾回收就是一个典型的守护线程。通过setDaemon(true)可以把一个用户线程设置为守护线程(在start之间)。

代码:

package demo01;

public class DaemonTest {
    public static void main(String[] args) {
        MyThread4 myThread4 = new MyThread4();
        Thread t1 = new Thread(myThread4,"线程1");
        Thread t2 = new Thread(myThread4,"线程2");

        //将t2设置为守护线程
        t1.start();
        t2.setDaemon(true);
        t2.start();
    }
}
class MyThread4 implements Runnable{

    //打印1-100
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

执行结果:线程1结束后,线程2并不是马上结束,而是执行了一段时间才结束。

7.线程的生命周期 8.线程的安全问题 

当多个线程操作同一个共享数据时就会出现线程的安全问题,上面的买票案例中已经存在安全问题了,因为多个窗口共享了100张票。

下面在通过一个案例理解线程的安全问题:

package demo02;

public class AccountTest {
    public static void main(String[] args) {
        Account account=new Account();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.getMoney(100);
            }
        }).start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.setMoney(100);
            }
        }).start();
    }
}

class Account{
    //账户余额
    private int money=0;
    //取钱
    public void getMoney(int num){
        if(money>=num){
            money-=num;
            System.out.println(Thread.currentThread().getName()+"取了"+num+",余额为:"+money);
        }
    }
    //存钱
    public void setMoney(int num){
        money+=num;
        System.out.println(Thread.currentThread().getName()+"存了"+num+",余额为:"+money);
    }

}


执行结果:

分析:两个线程分别对同一个账户存钱和取钱,当他们交替进行时, 就会出现问题,比如:一个线程正在取钱100,余额减少后,被另一个线程抢去了执行权,并执行了存钱操作,当再次回到该线程时,取钱后会发现余额不变,因为中间被另一个线程又存了100.

9.synchronized解决线程安全问题

线程出现安全问题的根本在于,多个线程对同一个资源进行操作时,一个线程操作数据时可能被另一个线程抢去执行权,等再回到该线程时,数据已经发生变化。造成数据错误。

如果能让某个线程执行某个操作时不被其他线程抢占cup资源,等到该操作执行完成后,才能被其他线程抢占,则能解决线程安全问题。

对上面存钱取钱案例进行改进:仅仅只是在存钱和取钱的两个方法上添加了synchronized关键字。

package demo02;

public class AccountTest2 {
    public static void main(String[] args) {
        Account2 account2=new Account2();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account2.getMoney(100);
            }
        }).start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account2.setMoney(100);
            }
        }).start();
    }
}

class Account2{
    //账户余额
    private int money=0;
    //取钱
    public synchronized void getMoney(int num){
        if(money>=num){
            money-=num;
            System.out.println(Thread.currentThread().getName()+"取了"+num+",余额为:"+money);
        }
    }
    //存钱
    public synchronized void setMoney(int num){
        money+=num;
        System.out.println(Thread.currentThread().getName()+"存了"+num+",余额为:"+money);
    }

}


执行结果:

synchronized关键字有两种用法,一是用在方法上,如上述代码,二是同步代码块,如:

 public  void getMoney(int num){
        synchronized (this) {
            if(money>=num){
                money-=num;
                System.out.println(Thread.currentThread().getName()+"取了"+num+",余额为:"+money);
            }
        }
    }

同步代码块中的括号里的this叫做同步监视器(俗称锁),同步监视器可以用任意对象进行充当。只有当线程获得了锁后才能进入同步代码块中执行具体代码,如,当线程1执行存钱方法时,先获得锁,然后在执行具体的存钱代码,在这期间,如果被另一个线程抢去了cpu资源,另一个线程尝试执行取钱操作,但由于锁已经被线程1获得,所以线程2无法继续执行,并进入等待锁的阻塞状态,这时,只有当线程1重新获得cpu执行权,并继续执行完同步代码块中的内容后释放锁,线程2才有可能获得锁并继续执行。线程安全问题得以解决。

同步的局限性:当执行同步代码块时,只能有一个线程执行,相当于串行,效率较低。

注意事项:

多个线程必须要共用同一把锁。

同步的代码内容不能少,也不能过多,过多会影响效率,过少不安全。

synchronized作用在方法上和同步代码块中的异同:

synchronized作用在普通方法上时锁为this,作用在静态方法上时锁为当前类的Class对象

synchronized作用在方法上时相当于这个方法的所有代码都是同步的。

synchronized获取锁和释放锁都是自动的。释放锁只有两种情况:

        1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

     2)线程执行发生异常,此时JVM会让线程自动释放锁

10.线程死锁问题 

形成原因:不同的线程分别占有对方需要的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。

死锁代码举例:

package demo02;

//死锁演示
public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        StringBuffer s1=new StringBuffer();
        StringBuffer s2=new StringBuffer();
        //线程1
        new Thread(() -> {
            synchronized (s1) {
                s1.append("a");
                s2.append("1");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s2) {
                    s1.append("b");
                    s2.append("2");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();

        //线程2
       new Thread(()->{
            synchronized (s2){
                s1.append("c");
                s2.append("3");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (s1){
                    s1.append("d");
                    s2.append("4");
                    System.out.println(s1);
                    System.out.println(s2);
                }
            }
        }).start();

    }
}

执行结果:大概率出现死锁

 

分析:线程1执行到sleep时阻塞100ms,此时若线程2获得cpu执行权,则执行到sleep也会阻塞,当两个线程醒来后,发现线程1需要的锁s2在线程2手中,线程2需要的锁s1在线程1手中,则相互等待对方释放资源,导致死锁。 

11.Lock方式解决线程安全问题 

        Lock是一个接口,表示锁,它有很多实现类,这里使用ReentrantLock来举例。

package demo01;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//jdk5.0新增Lock锁
class Window implements Runnable{

    private int ticket=100;

    private Lock lock=new ReentrantLock();

    @Override
    public void run() {
        while (true){

            try {
                //加锁
                lock.lock();
                if(ticket>0){

                    System.out.println(Thread.currentThread().getName()+":"+"出售第"+ticket+"张票");
                    ticket--;
                }else {
                    break;
                }
            } finally {
                //解锁
                lock.unlock();
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);
        t1.start();
        t2.start();
        t3.start();
    }


}

Lock使用lock()方法和unlock方法来手动实现加锁和解锁,相比synchronized更灵活。

三种实现线程安全的方式推荐使用顺序:Lock=>synchronized代码块=>synchronized方法

12.线程通信 

需求:两个线程交替打印1-100      分析:线程的执行是随机的,如果向要线程按照我们想要的顺序去执行,则需要使用线程通信。

案例代码:

package demo01;

//线程通信案例;使用两个线程交替打印1-100
class Number implements Runnable{
    private int num=1;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                //唤醒其他线程
                notify();
                if(num<=100){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;
                    try {
                    //线程等待阻塞,会释放锁
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}

执行结果:按照需求交替打印。

分析:线程1打印后等待并释放锁,线程2执行,先唤醒线程1,但此时锁在线程2手中,线程1无法执行,线程2继续执行并打印,然后等待并释放锁,线程1执行,循环往复。 

 

        

             

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/862832.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号