Java 线程

多线程

大纲   

1、多线程的创建方法
a.继承Thraad
b、实现Runnable接口
c、实现Callable接口
2、线程中的常见方法
3、线程的并发问题
4、Volatiel的作用及用法
5、synchronized的多种写法
a、public synchronized void test()
b、public synchronized static void test()
c、直接锁执行的任务内容
6、线程死锁
7、 wait() notify() notifyAll()的作用及用法
8、 线程的状态
9、如何让线程以固定顺序运行
10、java.util.concurrent.* java并发工具包
a、 线程池的4中创建
(1) 创建固定大小的线程池:Executors.newFixedThreadPool(2);
(2)创建缓冲线程池:Executors.newCachedThreadPool()
(3)创建单线程线程池:Executors.newSingleThreadExecutor()
(4)带有日程安排功能的线程池: Executors.newScheduledThreadPool(5);
b、原子操作类
c、线程安全集合类
d、ThreadLocal

应用程序以进程为单位运行,一个进程之内可以分为一到多个线程
操作系统中有一个组件叫做任务调度器,将cpu的时间片分给不同的程序使用, 微观串行(单核),宏观并行.(多核cpu可以并行)
好处:
1) 多进程,多线程可以让程序不被阻塞.
2) 充分利用多核cpu的优势,提高运行效率

多线程的创建方法

Thread 类

创建线程方法1:

Thread t = new Thread() {
    public void run() { 
        // 线程需要执行的代码
    }
};

启动线程 t.start()
该方法不能抛出检查异常,因为父类没有。

并行(parallel)和并发(concurrent)

创建线程的方法2:

实现Runnable接口,重写Run方法

Runnable runnable = new Runnable() {
    public void run(){
    }
};
Thread t = new Thread( runnable );
t.start();
//将接口子类对象当成参数传给Thread,然后进行启动

例子:

/*Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        };*/

        Runnable r = () -> System.out.println("hello");

        // () -> System.out.println("hello");

        Thread t = new Thread( r );
        t.start();

2. 线程中的常见方法

Thread.sleep(long n); // 让当前线程休眠n毫秒
Thread.currentThread(); // 找到当前线程
main 方法实际是由主线程(main)来调用的

注意 可以使用jconsle 来查看某个java进程中线程的运行情况
实例方法:
start() 让线程启动, 只能调用一次,如果调用了多次会出现IllegalThreadStateException
直接调用run和使用start间接调用run的区别:
(1)直接调用run是在主线程中执行了run,没有启动新的线程
(2)使用start是启动新的线程,通过新的线程间接执行run

join() 等待某个线程执行结束,例:

static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1= new Thread(()->{
            System.out.println("t1运行开始");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1运行结束");
            r = 100;
        });
        t1.start();

        /*System.out.println("main开始");
        t1.join();
        System.out.println("main结束");*/
        t1.join();
        System.out.println(r);
    }

join(long n) 表示等待线程结束,但是最多等待n毫秒
其它方法:
getName() 得到线程的名称
yield() 谦让
不推荐使用的方法
.stop() 让线程停止
.suspend() 让线程暂停
.resume() 让线程继续
默认情况下,java进程需要等待所有线程都运行结束,才会结束.
有一种特殊的线程叫做守护(守护的是主线程)线程,只要主线程运行结束,即使守护线程的代码没有执行完,也会跟着主线程一起结束,例:

Thread t1= new Thread(()->{
    System.out.println("守护线程开始执行...");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("守护线程结束");
});
t1.setDaemon(true); // 设置该线程为守护线程
t1.start();
Thread.sleep(1000);

interrupt() 可以打断正在等待的线程(包括sleep, join的等待)例如:

Thread t = new Thread(()->{
    try {
        Thread.sleep(5000); // 被打断线程会抛出InterruptedException
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("结束 。。。");
});
t.start();
Thread.sleep(1000);
System.out.println("打断t线程。。。");
t.interrupt();

3. 线程的并发(Concurrent)

synchronized (同步关键字)
语法

synchronized(对象也可以理解为锁) {
    要作为原子操作代码
}

用synchronized 解决并发问题:

static int i = 0;
static Object obj = new Object(); // 房间,能容纳一个人

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> { // 甲
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { // 甲会锁住这个房间
                i++;
            } // 甲从房间出来解开了锁
        }
    });
    Thread t2 = new Thread(() -> { // 乙
        for (int j = 0; j < 5000; j++) {
            synchronized (obj) { // 乙   在门外等待
                i--;
            } //
        }
    });
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}

每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器。要检查有没有owner,如果没有,此线程成为owner; 但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。
成为owner可以理解为获得了对象的锁
在竞争的时候,是非公平的

synchronized必须是进入同一个对象的monitor 才有上述的效果

4. volatile 易变的

注意:每个线程都存在一个高速缓存区
Volatile可以用来修饰成员变量和静态成员变量,他可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值。
它保证的是变量在多个线程之间的可见性, 不能保证原子性

static boolean run = true;
public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(()->{
        while(run){
            // ....
        }
    });
    t.start();

    Thread.sleep(1000);
    run = false;
}

一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止;
当run变量前有Volatiel修饰时,就不会出现这种情况了。

synchronized 语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能会受到影响。

5. synchronized的另外两种写法

public synchronized void test() {

}
等价于
public void test() {
    synchronized(this) {
    
    }
}//锁的是调用该方法的对象

 

class Test{
    public synchronized static void test() {

    }
}
等价于
public static void test() {
    synchronized(Test.class) {
        
    }
}

//锁的是字节码文件.class

6. 线程死锁

a 线程 获得 A 对象 锁
接下来获取B对象的锁
b 线程获得 B对象 锁
接下来获取A对象的锁
例:

Object A = new Object();
Object B = new Object();


Thread a = new Thread(()->{
    synchronized (A) {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (B) {
            System.out.println("操作...");
        }
    }
});

Thread b = new Thread(()->{
    synchronized (B) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (A) {
            System.out.println("操作...");
        }
    }
});
a.start();
b.start();

检测死锁可以使用 jconsole工具

7. wait() notify() notifyAll()

都属于Object对象的方法

wait() 等待
notify() 唤醒
它们都是线程之间进行协作的手段
obj.wait(); 让object监视器的线程等待
obj.notify(); 让object上正在等待的线程中挑一个唤醒
obj.notifyAll(); 让object上正在等待的线程全部唤醒

必须获得此对象的锁,才能调用这几个方法

Object obj = new Object();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-0线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-0其它代码....");
    }
}).start();

new Thread(()-> {
    synchronized (obj) {
        System.out.println("thread-1线程执行....");
        try {
            obj.wait(); // 让线程在obj上一直等待下去
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread-1其它代码....");
    }
}).start();

try {
    Thread.sleep(2000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj){
//            obj.notify();
    obj.notifyAll();
}

}

wait() 方法实际是会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止
wait(long n) 有时限的等待, 到n毫秒后结束等待,或是被notify

面试题:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒
1) sleep是Thread方法,而wait是Object的方法
2) sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用
3) sleep 在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁。
sleep及前者得等候一个线程执行完后才能执行后续线程;wait及后者无需等待,可以先执行后续线程。

8. 线程的状态

NEW(新建) 线程刚被创建,但是还没有调用 start方法

RUNNABLE(可运行) 当调用了start() 方法之后

BLOCKED(阻塞) 当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态

WAITING(等待) 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态

TIMED_WAITING 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会

TERMINATED (终止)当线程代码运行结束

五种状态:
NEW(新建), RUNNABLE(可运行) , RUNNING(正在运行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(终止)

9. 如何让两个线程以固定的顺序运行

static Object obj = new Object();
static boolean t2runed = false;// t2是否执行过
// 打印 2, 1

public static void main(String[] args) {

    Thread t1 = new Thread(() -> {
        synchronized (obj) {
            while(!t2runed) { // 如果t2没有执行过
                try {
                    obj.wait(); // 线程t1 先等一会
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(1);
    });

    Thread t2 = new Thread(()->{
        System.out.println(2);
        synchronized (obj) {
            t2runed = true;
            obj.notify();
        }
    });

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

java.util.concurrent.* java并发工具包

1. 创建线程的第三种方式

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

与Runnable的区别,1)有返回结果,2)可以抛出检查异常

创建代码:

// 代表一个任务对象
// Callable代表线程中要执行的代码
FutureTask task = new FutureTask(new Callable() {
    @Override
    public Object call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"开始执行");
        Thread.sleep(2000);
        return "ok";
    }
});

// 创建和启动新线程
new Thread(task).start();

// 获取返回结果
System.out.println(task.get());

2. 线程池

创建有限的线程资源为更多的任务提供服务。享元模式

// java中对线程池的抽象:ExecutorService  创建一个固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);

/*for (int i = 0; i < 10; i++) {
    threadPool.submit(()->{
        System.out.println(Thread.currentThread().getName()+"任务执行");
    });
}*/
// 执行带有返回结果的任务
Future future = threadPool.submit(() -> {
    System.out.println(Thread.currentThread().getName()+"执行计算...");
    Thread.sleep(1000);
    return 10;
});
System.out.println(future.get());


threadPool.shutdown(); // 不接收新任务,当所有任务运行结束,整个线程池关闭

一个核心的ExecutorService的实现类:ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

corePoolSize 核心线程数目 (最多保留的线程数)
5
maximumPoolSize
10

workQueue 阻塞队列 如果任务超过了核心线程数,进入队列进行排队,直到有空闲的线程
10

如果任务过多,阻塞队列都放不下了,还会创建新的线程来救急
corePoolSize+救急的线程 <= maximumPoolSize(最大线程数)
21 会抛出拒绝提交任务异常

keepAliveTime 生存时间- 针对救急线程
60
unit 时间单位 秒

// 创建固定大小的线程池
Executors.newFixedThreadPool(2);
核心线程数=最大线程数(没有救急线程被创建)
阻塞队列 无界,可以放任意数量的任务,
适合执行数量有限,长时间运行的任务

// 创建缓冲线程池
Executors.newCachedThreadPool()
核心线程数是0, 最大线程数是Integer的最大值(救急线程可以无限创建)
生存时间是60s
适合任务数比较密集,但每个任务执行时间较短的情况

// 创建单线程线程池
Executors.newSingleThreadExecutor()
使用场景:希望多个任务排队执行

区别:
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改

// 带有日程安排功能的线程池

ScheduledExecutorService service = Executors.newScheduledThreadPool(5);

// 让任务推迟一段时间执行
// 参数1.任务对象, 参数2,3 推迟的时间
/*service.schedule(()->{
    System.out.println("执行任务...");
}, 10L, TimeUnit.SECONDS);*/

// 以一定的频率反复执行任务(任务不会重叠)
// 参数1,任务对象, 参数2,初始推迟时间, 参数3,4 时间间隔和单位
/*service.scheduleAtFixedRate(()->{
    try {
        Thread.sleep(1200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);*/

// delay表示从上一个任务结束,到下一个任务开始之间的时间
service.scheduleWithFixedDelay(()->{
    System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);

//        service.shutdown();

3. 原子操作类

AtomicInteger
AtomicBoolean
...

// 创建原子整数类
private static AtomicInteger i = new AtomicInteger(0);

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            i.getAndIncrement();  // 获取并且自增  i++
//                i.incrementAndGet();  // 自增并且获取  ++i
        }
    });

    Thread t2 = new Thread(() -> {
        for (int j = 0; j < 5000; j++) {
            i.getAndDecrement(); // 获取并且自减  i--
        }
    });

    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}

4. 线程安全集合类

StringBuffer 线程安全
String 不可变类 , 都是线程安全的
Random 线程安全
Vector 实现了List,并且线程安全
Hashtable 实现了Map,并且线程安全

5.0新增的线程安全集合类
ConcurrentHashMap 实现了Map,并且线程安全
ConcurrentSkipListMap 实现了Map(可排序),并且线程安全
CopyOnWriteArrayList 实现了List,并且线程安全

BlockingQueue 阻塞队列
队列 FIFO , first in first out

Queue --> LinkedList

private static BlockingQueue<Product> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {

    // 生产者线程
    new Thread(()->{
        for (int i = 0; i < 10; i++) {
            Product p = new Product(i);
            System.out.println(Thread.currentThread().getName()+"生产了:"+p);
            try {
                queue.put(p);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }).start();

    // 消费者线程
    for (int j = 0; j < 5; j++) {
        new Thread(()->{
            for (int i = 0; i < 2; i++) {
                try {
                    Product p = queue.take();
                    System.out.println(Thread.currentThread().getName()+"消费了:"+p);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }


}

5. ThreadLocal

//             线程局部变量
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal() {
@Override        // 初始值
protected Object initialValue() {
    return new SimpleDateFormat("yyyy-MM-dd"); // 存入当前线程
}
};


public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
    new Thread(()->{
        try {
            SimpleDateFormat sdf = local.get(); // 获取本线程自己的局部变量
            Date date = sdf.parse("1951-10-09"); // 每个线程使用的是自己的SimpleDateFormat因此没有争用
            System.out.println(Thread.currentThread().getName() + " " + date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }).start();
}

/*for (int i = 0; i < 10; i++) {
    new Thread(()->{
        try {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = sdf.parse("1951-10-09");
            System.out.println(Thread.currentThread().getName() + " " + date);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }).start();
}*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值