欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JAVA多线程——(一)多线程编程

程序员文章站 2022-05-05 22:09:01
...

JAVA多线程——多线程编程

【一】线程创建

  • Thread创建:

如果要使用Thread来创建线程,需要新建一个类来继承Thread,重新run方法

class MyThread extends Thread {
    
    /**
     * 这是线程任务 
     */
    @Override
    public void run() {
        System.out.println("这是新建线程");
    }
}

public class Main {
    public static void main(String args[]){
        //创建线程
       MyThread myThread = new MyThread();
       //执行线程任务
       myThread.start();
    }
}
  • Runnable
    使用runnable需要写一个实现这个接口的类,但是这种方法是没返回值的
class MyTask implements Runnable {

    /**
     * 这是线程任务
     */
    @Override
    public void run() {
        System.out.println("这是新建线程");
    }
}

public class Main {
    public static void main(String args[]) {
        //创建任务
        MyTask task = new MyTask();
        //创建线程
        Thread thread = new Thread(task);
        
        //执行线程任务
        thread.start();
    }
}

【二】线程状态转换

JAVA多线程——(一)多线程编程

  • 1、新建状态(New):新创建了一个线程对象。

  • 2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于“可运行线程池”中,变得可运行,只等待获取CPU的使用权。即在就绪状态的进程除CPU之外,其它的运行所需资源都已全部获得。

  • 3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。

  • 4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态

  • 5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

详细线程状态转换请移步到博客:https://blog.csdn.net/wenge1477/article/details/90481125

【三】守护线程

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。Daemon的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

java中线程包括两种:
1、User Thread(用户线程)
2、Daemon Thread(守护线程)

class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println("这是新建线程");
    }
    
}

public class Main {
    public static void main(String args[]) {
        //创建任务
        MyTask task = new MyTask();
        //创建线程
        Thread thread = new Thread(task);

        //设置为守护线程
        thread.setDaemon(true);

        //执行线程任务
        thread.start();
    }
}

  • // 设定 daemonThread 为 守护线程,default false(非守护线程)
 daemonThread.setDaemon(true);  
  • // 验证当前线程是否为守护线程,返回 true 则为守护线程
 daemonThread.isDaemon();  

【四】线程同步

即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种

  • synchronied
    synchronied进行对象锁,锁住对象,
  • ReentranLock
  • ReadWriteLock
  • wait和notify
  • condition
  • 阻塞队列
  • CountDownLatch
  • CyclicBarrier

【五】线程死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。各自持有资源不释放,确申请获取对方手中的资源,形成相互等待

  • 互斥条件:一个资源每次只能被一个进程使用。

  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  • 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。

  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

死锁例子:

public class DeadLockSample extends Thread{
    private String first;
    private String second;
    public DeadLockSample (String name,String first,String second){
        super(name);
        this.first = first;
        this.second = second;
    }

    public void run(){
        synchronized (first){
            System.out.println(this.getName() + " obtained:" + first);

            try {
                Thread.sleep(1000L);
                synchronized (second){
                    System.out.println(this.getName() +" obtained: " + second);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        String lockA = "lockA";
        String lockB = "lockB";
        DeadLockSample t1 = new DeadLockSample("Thread1",lockA,lockB);
        DeadLockSample t2 = new DeadLockSample("Thread2",lockB,lockA);
        t1.start();
        t2.start();
        t1.join();
        t2.join();

    }

}
结果:
Thread1 obtained:lockA
Thread2 obtained:lockB
或者
Thread2 obtained:lockB
Thread1 obtained:lockA


预防死锁的几种方法:

  • 加锁顺序(线程按照一定的顺序加锁)
    如果能确保所有的线程都是按照相同的顺序获得锁,那么死锁就不会发生

  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
    在申请资源的时候设置时间限制,当时间到了就不在申请资源,比如lock的tryLocak方法可以设置时间

  • 死锁检测

【六】synchronied

synchronized关键字是java并发编程中必不可少的工具,synchronized是围绕一个被称为内部锁或监视锁的内部实体实现的(Api规范里经常将其简单的称之为“monitor”)。内部锁在同步的两个方面发挥作用:强制独占访问对象状态和建立对可见性必不可少的happens-before关系

  • 同步代码块
    锁住的是一个对象
1、第一种
 public void getCunt(){
        synchronized (this){
            System.out.println("这是同步代码块!!!");
        }
}

2、第二种
 public synchronized void getCunt() {
        System.out.println("这是同步代码块!!!");
}
  • 同步方法
    锁住的是当前的类的class
1、第一种
public void getCunt() {
        synchronized (Main.class) {
            System.out.println("这是同步代码块!!!");
        }
}

2、第二种
public static synchronized void getCunt() {
        System.out.println("这是同步代码块!!!");
    }

【七】wait和notify

public class Main {
    public static void main(String args[]){
        String lock = new String();

        Stack<Integer> stack = new Stack<>();
        AtomicInteger i = new AtomicInteger(1);

        //生产者生产数据
        Thread producer = new Thread(() -> {
            synchronized (lock) {
                while (stack.isEmpty()) {
                    stack.push(new Integer(i.getAndIncrement()));
                    lock.notifyAll();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        //消费者消费数据
        Thread consumer = new Thread(() -> {
            synchronized (lock) {
                while (!stack.isEmpty()) {
                    System.out.print(stack.pop()+" ");
                    lock.notifyAll();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        producer.start();
        consumer.start();
    }
}


【八】链接

https://blog.csdn.net/J080624/article/details/82721827
https://www.jianshu.com/p/f2c91afe6266