了解多线程(一)

进程和线程的区别:

  • 每个进程是一个应用程序,都有独立的内存空间,多进程的作用不是提高执行速度,而是提高 CPU 的使用率。
  • 同一个进程中的线程共享其进程中的内存和资源(共享的内存是堆内存和方法区内存,栈内存不共享),多线程不是为了提高执行速度,而是提高应用程序的使用率.感觉多个线程在同时并发执行;CPU同一时刻只有一个线程在执行,Java多线程是通过抢占式线程调度来实现线程之间的高速切换;
  • 线程和线程共享”堆内存和方法区内存”.栈内存是独立的,一个线程一个栈.

了解多线程(一)_第1张图片

1、新建态,通过上述几种方式创建了具有线程执行体的Thread对象,就进入了新建态。

2、就绪态,调用Thread对象的start()方法,就会为线程分配线程私有的方法栈、程序计数器资源,如果得到CPU资源,线程就会由就绪态转为运行态。换句话说,就绪态的线程获得了除CPU之外的所有必须资源。

3、运行态,就绪态线程得到CPU资源就会转为运行态,执行run()方法。当然,在调用yield()线程让步的情况,线程会由运行态转到就绪态,但这个过程可能是及其短暂的,如果当前线程拥有较高的优先级,即使让步后,它也会直接转为运行态。

4、阻塞态会导致阻塞态的方法主要有:sleep()方法、wait()方法、join()方法、等待获取锁、等待IO等情况。在这些情况被处理后,就会转为就绪态,等待调度。

5、终止态,自然终止,run()方法执行结束后,线程自动终止  \  使用volatile 标志位(其实就是外部控制的自然死亡)\  使用interrupt()方法中断运行态和阻塞态线程。

并行与并发的区别:

并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

线程安全问题:由于多线程的资源是共享,能够保证在同一时刻最多只有一个线程执行某段代码,以达到保证并发安全的效果。线程同步可以解决线程安全问题.

同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。加入@synchronized关键字,在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。

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


线程常用方法:

sleep

sleep 设置休眠的时间,单位毫秒,当一个线程遇到 sleep 的时候,就会睡眠,进入到阻塞状态,放弃 CPU,腾出 cpu 时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得 CPU 时间片,当睡眠时间到达了,线程会进入可运行状态,得到 CPU 时间片继续执行,如果线程在睡眠状态被中断了,将会抛出 IterruptedException

Thread.sleep(毫秒);

sleep()方法是Thread类下的方法,控制线程休眠,休眠过程中不会释放锁,sleep()时间到后进入就绪态等待调度。

wait()方法是Object类下的方法,控制线程等待,等待过程会释放锁,被notify()后会进入就绪态等待调度。

wait()方法为什么属于Object类,而不是Thread类,是因为:

wait()方法用于多个线程争用一把锁(一般为Synchronized锁住的对象)的情况,同一时刻只有一个线程能够获得锁,其他线程就要在线程队列等待。作用对象就是被锁住的对象,所以线程队列的维护工作应该交给Object。如果交给Thread,那么每个Thread都要知道其他Thread的状态,这并不合理。

停止一个线程:interrupt()

如果我们的线程正在睡眠,可以采用 interrupt 进行中断通常定义一个标记,来判断标记的状态停止线程的执行

使用interrupt()方法中断运行态和阻塞态线程。(关于interrupt(),调用interrupt()方法,立刻改变的是中断状态,但如果不是在阻塞态,就不会抛出异常;如果在进入阻塞态后,中断状态为已中断,就会立刻抛出异常。但是在获取synchronized锁的过程中不可被中断。

当线程A执行到wait(),sleep(),join()时,抛出InterruptedException后,中断状态已经被系统复位了,线程A调用Thread.interrupted()返回的是false。

如果线程被调用了interrupt(),此时该线程并不在wait(),sleep(),join()时,下次执行wait(),sleep(),join()时,一样会抛出InterruptedException,当然抛出后该线程的中断状态也会被系统复位。

注意:当线程抛出一个未被捕获的异常或错误时,线程会异常终止。

 /**
     * 某线程正在休眠,如何打断它的休眠 以下方式依靠的是异常处理机制
     */
    private static void ThreadTest4() {
        try {
            Thread t = new Thread(new Processor4());
            t.start();
            Thread.sleep(5000);// 睡5s
            t.interrupt();// 打断Thread的睡眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 如何正确的更好的终止一个正在执行的线程 需求:线程启动5s之后终止.
     */
    private static void ThreadTest5() {
        Processor5 p = new Processor5();
        Thread t = new Thread(p);
        t.start();
        // 5s之后终止
        try {
            Thread.sleep(5000);
            p.isRun = false;
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

yield

它与 sleep()类似,只是不能由用户指定暂停多长时间,并且 yield()方法只能让同优先级的线程有执行的机会,采用 yieid 可以将 CPU 的使用权让给同一个优先级的线程

join

当前线程可以调用另一个线程的 join 方法,调用后当前线程会被阻塞不再执行,直到被调用的线程执行完毕,当前线程才会执行

synchronized

线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后,再让线程二来使用 s 变量

异步编程模型 : t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁.
同步编程模型 : t1线程和t2线程执行,t2线程必须等t1线程执行结束之后,t2线程才能执行,这是同步编程模型.

什么时候要用同步呢?为什么要引入线程同步呢?
1.为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制.
线程同步机制使程序变成了(等同)单线程.


2.什么条件下要使用线程同步?
第一: 必须是多线程环境
第二: 多线程环境共享同一个数据.
第三: 共享的数据涉及到修改操作.

public class SynchronizedTest {
    public static void main(String[] args) {
        SynchronizeTest1();
    }

    private static void SynchronizeTest1() {
        Account account=new Account("Actno-001",5000.0);
        Thread t1=new Thread(new Processor(account));
        Thread t2=new Thread(new Processor(account));
        t1.start();
        t2.start();
    }

}
/**
 * 取款线程
 */
class Processor implements Runnable{
    Account act;
    Processor(Account act){
        this.act=act;
    }
    @Override
    public void run() {
        act.withdraw(1000.0);
        System.out.println("取款1000.0成功,余额: "+act.getBalance());
    }

}
class Account {

    private String actno;
    private double balance;

    public Account() {
        super();
    }

    public Account(String actno, double balance) {
        super();
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 对外提供一个取款的方法 对当前账户进行取款操作
     */
    public void withdraw(double money) {
        //把需要同步的代码,放到同步语句块中.
        //遇到synchronized就找锁,找到就执行,找不到就等
        /**
         * 原理: t1线程和t2线程
         * t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
         * 如果找到this对象锁,则进入同步语句块中执行程序,当同步语句块中的代码执行结束之后,
         * t1线程归还this的对象锁.
         * 
         * 在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到synchronized关键字,
         * 所以也去找this对象锁,但是该对象锁被t1线程持有,只能在这等待this对象的归还.
         * 
         * synchronized关键字添加到成员方法上,线程拿走的也是this的对象锁.
         * 
         */
        synchronized (this) {
            double after = balance - money;
            try {
                //延迟
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新
            this.setBalance(after);
        }
    }
}
public class SynchronizedTest2 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc1=new MyClass();
        MyClass mc2=new MyClass();
        Thread t1=new Thread(new Runnable1(mc1));
        Thread t2=new Thread(new Runnable1(mc2));
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        //延迟,保证t1先执行
        Thread.sleep(1000);
        t2.start();
    }
}
class Runnable1 implements Runnable{
    MyClass mc;
    Runnable1(MyClass mc){
        this.mc=mc;
    }
    @Override
    public void run() {
        if("t1".equals(Thread.currentThread().getName())){
            MyClass.m1();//因为是静态方法,用的还是类锁,和对象锁无关
        }
        if("t2".equals(Thread.currentThread().getName())){
            MyClass.m2();
        }
    }
}
class MyClass{
    //synchronized添加到静态方法上,线程执行此方法的时候会找类锁,类锁只有一把
    public synchronized static void m1(){
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1()............");
    }
    /**
     *  m2()不会等m1结束,因为该方法没有被synchronized修饰
     */
//    public static void m2(){
//        System.out.println("m2()........");
//    }
    /**
     * m2方法等m1结束之后才能执行,该方法有synchronized
     * 线程执行该方法需要"类锁",而类锁只有一个.
     */
    public synchronized static void m2(){
        System.out.println("m2()........");
    }
}


Timer.schedule()

/**
 * 关于定时器的应用 作用: 每隔一段固定的时间执行一段代码
 */
public class TimerTest {

    public static void main(String[] args) throws ParseException {
        // 1.创建定时器
        Timer t = new Timer();
        // 2.指定定时任务
        t.schedule(new LogTimerTask(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").parse("2017-06-29 14:24:00 000"), 10 * 1000);
    }
}

// 指定任务
class LogTimerTask extends TimerTask {

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

ConcurrentLinkedQueue,它是线程安全的集合,允许并发的访问数据结构的不同部分来使竞争极小化。这些集合返回弱一致性的迭代器。这意味着迭代器不一定能反映出他们被构造之后的所有的修改,但是,他们不会将同一个值返回两次。


Java创建4种多线程对比:使用Callable接口和Future创建线程:常用

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或者其子类实例。每个线程的作用是完成一定的任务,实际上是执行一段程序流.

Thread中的run方法不能抛出异常,所以重写runn方法之后,在run方法的声明位置上不能使用throws 所以run方法中的异常只能try...catch...

1. 继承Thread类创建线程类:

步骤:

  1. 定义Thread类的子类,并重写该类的run()方法。
  2. 创建子类的实例,即创建了线程对象。
  3. 调用线程对象的start()方法来启动该线程。

这种方式每个线程都是独立的,,多线程就是分别完成自己的任务,并且Thread类具有Java类单继承带来的局限性(一个Java类不能继承两个类,不能有两种特性),.

  1. Java程序运行时有默认的主线程,它的方法体就是main()方法的方法体。

  2. 使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。

2.实现Runnable接口来创建线程类

Runnable实例对象作为Thread构造方法中的target参数传入,充当线程执行体。这种方式适用于多个线程共享资源的情况。

步骤:

  1. 定义Runnable接口的实现类,并重写run()方法。

  2. 创建Runnable实现类的实例,并将它作为Target传入Thread类的构造器以创建Thread对象。

  3. 调用线程对象的start()方法来启动线程。

使用这种方法,程序创建的Runnable对象只是线程的target,而多个线程可以共享同一个target,所以多个线程可以共享同一个线程类的实例变量。

实现多线程有两种方式:一种继承Thread,另外一种是实现Runnable.

一般都实现Runnable,主要是为了避免单继承带来的弊端,另外实现Runnable,不用sychronized就可以共享资源。取线程的名字,继承Thread直接就getName,实现Runnable,用Thread.currentThread().getName().因为操作 线程的主要方法都在Thread里面。

实现线程同步:

1、使用sychronized关键字,获取同步监视器的锁定。

2、显式加锁的方法Lock

3、使用wait让出线程,同时释放同步监视器的锁定,等另一个线程执行到一定条件,使用notify唤醒该线程

3、使用Callable接口和Future创建线程:常用

实现Runnable接口以创建线程优势:

1.Callable接口,是Runable接口的增强版。同样用Call()方法作为线程的执行体,增强了之前的run()方法。因为call方法可以有返回值,也可以声明抛出异常。

2.java5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,所以这样可以作为Thread的target。

3.运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

解读:

1.Callable接口不能直接作为Thread的target来使用;java5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,所以这样可以作为Thread的target。

2.Java 5中提供了Future接口来代表Callable接口中的call()方法的返回值,并且为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口——可以作为Thread  target(线程任务);

Future接口定义了几个公共方法用来控制和它关联的Callable任务:FutureTask实现了Future接口

boolean cancel(boolean mayInterruptIfRunning):试图取消该Future关联的Callable任务。
V get():获取关联的任务的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
V get(long timeout,TimeUnit unit):获取关联的任务的返回值。该方法最多让程序阻塞timeout和unit指定时间,如果经过指定时间后Callable任务仍然没有返回值,则将抛出TimeoutException异常。
boolean isCancelled():如果在Callable任务完成之前被取消,则返回true
boolean isDone():如果任务已经完成,则返回true

FutureTask的get和cancel的执行示意图:

了解多线程(一)_第2张图片

 Callable接口有泛型限制,接口中的泛型形参类型必须和call()方法返回值类型相同。而且Callable接口是函数式接口,因此可以使用Lambda表达式创建Callable对象。

接口实现关系发现:FutureTask —> RunnableFuture --> Runnable

1.实现 Callable接口,相较于实现 Runnable 接口的方式,优点是:方法可以有返回值,并且可以抛出异常

2.需要 FutureTask实现类的支持,用于接收运算结果

 3.result.get(),接收返回的计算结果,在所有的线程没有执行完成之后这里是不会执行的

实现步骤:

1、在声明接口时就需要指定泛型类型,且call()方法的返回值类型要与泛型类型一致。

2、使用FutureTask封装Callable实例对象,作为Thread构造方法中的target参数传入。

3、在调用start()后,可使用FutureTask的get()方法获取返回值。

public static void main(String[] args) {    
        //创建一个FutureTask list来放置所有的任务
        List> futureTasks=new ArrayList<>();
        for(Integer i=0;i<10;i++){
            MyTask myTask=new MyTask(i.toString(), i.toString());
            futureTasks.add(new FutureTask<>(myTask));
        }
        
        //创建线程池后,依次的提交任务,执行
        ExecutorService executorService=Executors.newCachedThreadPool();
        for(FutureTask futureTask:futureTasks){
            executorService.submit(futureTask);
        }
        executorService.shutdown();
        
        //根据任务数,依次的去获取任务返回的结果,这里获取结果时会依次返回,若前一个没返回,则会等待,阻塞
        for(Integer i=0;i<10;i++){
            try {
                String flag=(String)futureTasks.get(i).get();
                System.out.println(flag);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    } 
  

 

你可能感兴趣的:(多线程)