目录
一、并发、并行、进程、线程概念。
1.并发与并行
并发:指两个或多个事件在同一时间段内发生。
并行:指两个或多个事件在同一时刻发生。
2.线程与进程
进程:指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,即进程空间或(虚空间)。
线程:线程是进程中的一个执行单元。一个程序运行后至少会有一个进程,一个进程中可以包含多个线程。
3.线程调度
1.分时调度:平分CPU的时间片。
2.抢占
二、创建线程
1.继承Thread类
式调度:优先级高的概率大(使用最多的方式)
package com.tuling.part1;
//step1:继承自Tread类。
class MyThread extends Thread {
@Override
public void run() {
//线程执行体。
for (int i = 0; i < 20; i++) {
System.out.println("新建线程:"+i);
}
}
}
//step2:创建MyTread
public class Demo1 {
public static void main(String[] args) {//程序方法的入口
MyThread myThread = new MyThread();
//step3:调用start()方法。
myThread.start();//相当于开启一个新的线程,这个新的线程和main线程是平级的。
//myThread.run();//这样写是普通方法的调用。
for (int i = 0; i < 20; i++) {
System.out.println("主线程:"+i);
}
}
}
2.实现Runable接口
package com.tuling.part1;
//step1:实现runable接口
class MyRunable implements Runnable {
@Override
public void run() {
//线程的执行体
for (int i = 0; i < 10; i++) {
System.out.println("新建线程:"+i);
}
}
}
public class Demo2 {
public static void main(String[] args) {
//step2:创建实现类的对象
MyRunable myRun = new MyRunable();
Thread thread = new Thread(myRun);
//启动一个线程。
thread.start();
//主线程
for (int i = 0; i < 10; i++) {
System.out.println("主线程:"+i);
}
}
}
3.继承Thread和实现Runable的区别
1.创建线程的方式不同:
实现Runnable接口是代理模式,交给Thread()类去start;
new Thread(new MyRunnable(),"线程名").start();
一个类继承Thread类以后本身就是一个线程对象,可以直接start;
new MyThread().satrt();
2.设置线程名方式不同
实现Runnable接口可以在创建线程时Thread类的构造器设置线程名
new Thread(new MyRunnable(),"线程名").start();
继承Thread类,可以super()调用父类构造器起名区别三
3.获取线程名方式不同:
实现Runnable接口获取线程名方式:
System.out.println(Thread.currentThread().getName());
继承Thread类本身就是线程,获取线程名方式两种都可以:
System.out.println(Thread.currentThread().getName());
System.out.println(this.getName());
4.由于Java是单继承,一个类继承Thread类以后不能继承其他类,扩展性不好而实现Runnable接口则可以侧面实现了多继承
5.继承Thread类不能实现线程变量资源共享,注意,是线程里的变量实现Runnable 接口线程变量是可以共享的,也可以不共享,看创建线程的方式
售票小示例:
package com.tuling.part1;
//step1:实现runable接口
class MyRunable implements Runnable {
private int ticket = 12;
@Override
public void run() {
//线程的执行体
for (int i = 0; i < 4; i++) {
// System.out.println("新建线程:"+i);
System.out.println(Thread.currentThread().getName()+"卖了一张票,剩下:"+ticket--);
}
}
}
public class Demo2 {
public static void main(String[] args) {
//step2:创建实现类的对象
MyRunable myRun = new MyRunable();
Thread t1= new Thread(myRun,"窗口1");
Thread t2= new Thread(myRun,"窗口2");
Thread t3= new Thread(myRun,"窗口3");
//启动一个线程。
t1.start();
t2.start();
t3.start();
// //主线程
// for (int i = 0; i < 10; i++) {
// System.out.println("主线程:"+i);
// }
}
}
三、线程的常用方法
方法名
public static void sleep(long millis) //当前线程主动休眠millis毫秒。
public static void yield() //当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
public final void join() //允许其他线程加入当前线程中
public void setPriority(int)//线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
public void setDaemon(boolean)//设置为守护线程有两类:用户线程(前台线程)、守护线程(后台线程)。
1.线程的优先级
示例
package com.tuling.part2;
class PriorityThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"==========="+i);
}
}
}
public class TestPriority {
public static void main(String[] args) {
PriorityThread P1 = new PriorityThread();
P1.setName("P1线程");
PriorityThread P2 = new PriorityThread();
P2.setName("P2线程");
PriorityThread P3 = new PriorityThread();
P3.setName("P3线程");
//设置优先级
P1.setPriority(Thread.MAX_PRIORITY);
P3.setPriority(Thread.MIN_PRIORITY);
P1.start();
P2.start();
P3.start();
}
}
2.线程的休眠
示例
package com.tuling.part2;
class SleepThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("睡眠线程:"+i);
}
}
}
public class TestSleep {
public static void main(String[] args) {
SleepThread sleepThread = new SleepThread();
sleepThread.start();
//让当前线程睡眠十秒。
try{
Thread.sleep(1000*10);
}catch(InterruptedException e){
e.printStackTrace();
}//抓取睡眠异常。
//睡眠线程和主线程之间打印一条分割线。
System.out.println("--------------------------------------------------------");
for (int i = 0; i < 10; i++) {
System.out.println("主线程:"+i);
}
}
}
3.线程的让步
示例
package com.tuling.part3;
class Task1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("A:"+i);
}
}
}
class Task2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("B:"+i);
//让步(无法保证yield()达到让步的目的)
Thread.yield();//不会让A执行完了在执行B,没办法保证这一点。
}
}
}
public class TestYield {
public static void main(String[] args) {
// Task1 task1 = new Task1();
// thread.start();
//简化
//匿名对象的写法,这个方法只需要使用一次。
new Thread(new Task1()).start();
new Thread(new Task2()).start();
}
}
slee()和yield()的区别:
sleep()
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
yield()
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。而且,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
4.线程的合并
示例
package com.tuling.part4;
class JoinThread extends Thread {
public JoinThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"打印------>"+i);
}
}
}
public class TestJoin {
public static void main(String[] args) {
System.out.println("主线程开始执行....................");
JoinThread joinThread = new JoinThread("这是主线程!");
joinThread.start();
try{
joinThread.join();//调用主线程程序。(合并线程)
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("主线程结束执行.....................");
}
}
四、守护线程
守护线程setDaemon(true):设置守护线程。
线程有两类:用户线程(前台线程)、守护线程(后台线程)
如果程序中所有前台线程都执行完毕了,后台线程会自动结束。
垃圾回收器线程属于守护线程。
package com.tuling.part5;
class DeamonThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("守护线程:"+i);
}
try{
Thread.sleep(2000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public class TestDeamon {
public static void main(String[] args) {
//其中一个守护线程
DeamonThread deamonThread = new DeamonThread();
deamonThread.setDaemon(true);//就说明我这个是守护线程。
deamonThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("主线程打印:"+i);
}
}
}
五、线程生命周期
1.五种基本状态
1.新建状态(New)
2.就绪状态(Runable)
3.运行状态(Runing)
4.阻塞状态(Blocked)
5.死亡状态(Dead)
2.多线程状态之间的转换
1.就绪转换为运行
2.运行转换为就绪
3.运行转换为死亡
六、线程安全
1.同步代码块
语法:
synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}
同步锁
对象的同步锁只是一个概念可以想象为在对象上标记了一个锁。
1.对象锁可以是任意型。
2.多个线程对象要使用同一把锁。
注意:在任何时候最多允许一个线程有同步锁,谁拿到锁就进入代码块,其他线程只能在外面等着(BLOCKED)。
示例:
package com.tuling.part6;
class Ticket2 implements Runnable {
private int ticket = 100;
final Object lock = new Object();//创建锁lock
@Override
public void run() {
while(true){ //只要有票会一直卖
//模拟一下出票时间。
synchronized(lock){ //每次只允许一个线程进来执行里面的代码。
if( ticket>0 ){
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket--+"张票");
}
}
}
}
}
public class TicketDemo2 {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
Thread t1 = new Thread(ticket,"窗口1:");
Thread t2 = new Thread(ticket,"窗口2:");
Thread t3 = new Thread(ticket,"窗口3:");
t1.start();
t2.start();
t3.start();
}
}
2.同步方法
用synchronized修饰的方法。就是同步方法
package com.tuling.part6;
class Ticket3 implements Runnable {
private static int ticket = 100;
// final Object lock = new Object();//创建锁lock
@Override
public void run() {
while(true){ //只要有票会一直卖
//调用同步方法
sellTicket();
}
}
// public synchronized void sellTicket(){ //synchronized修饰普通方法,有锁:this
// //synchronized (this){}
// if( ticket>0 ){
// try{
// Thread.sleep(10);
// }catch(InterruptedException e){
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"卖了第"+ticket--+"张票");
// }
// }
public synchronized static void sellTicket(){ //synchronized修饰静态方法,有锁:ticket3.class
//synchronized (this){}
if( ticket>0 ){
try{
Thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket--+"张票");
}
}
}
public class TicketDemo3 {
public static void main(String[] args) {
// Ticket3 ticket = new Ticket3();
// Thread t1 = new Thread(ticket,"窗口1:");
// Thread t2 = new Thread(ticket,"窗口2:");
// Thread t3 = new Thread(ticket,"窗口3:");//这是普通方法
Ticket3 ticket1 = new Ticket3();
Ticket3 ticket2 = new Ticket3();
Ticket3 ticket3 = new Ticket3();
Thread t1 = new Thread(ticket1,"窗口1:");
Thread t2 = new Thread(ticket2,"窗口2:");
Thread t3 = new Thread(ticket3,"窗口3:");//这是修饰静态的方法。
t1.start();
t2.start();
t3.start();
}
}
3.Lock
与synchronized比较,显示定义,结构更加灵活。
提供更多的实用性方法,功能更强大、性能更优越。
常用方法:
描述 | 方法名 |
---|---|
获取锁,如果锁被占用则等待。 | void lock() |
尝试获取锁(成功返回true。失败返回false,不阻塞) | boolean tryLock() |
释放锁 | void() |
Lock接口的实现类,与synchronized一样具有互斥锁功能。(ReentrantLock)
示例:
package com.tuling.part6;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyList{
private Lock lock = new ReentrantLock();
private String[] strArr = {"A","B","c","d","e"};
private int index = 2;
public void add(String str) {
lock.lock();//手动加锁
try{//这里面包含代码异常块
strArr[index] = str;
//str与index++加上一点延迟。同时会出现异常
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
index++;
System.out.println(Thread.currentThread().getName() +"添加了"+str);
}finally {//finally保证锁的释放
lock.unlock();//释放锁 //
}
}
public String[] getStrArray() {
return strArr;
}
}
//class MyRunnable implements Runnable {
// @Override
// public void run() {
//
// }
//}
public class TestMyList {
public static void main(String[] args) {
MyList myList = new MyList();//创建mylist
//将实现类的对象赋值给接口
//Runnable myrunnable1 = new MyRunnable();
//匿名内部类
Runnable r1 = new Runnable() { //将匿名内部类赋值给Runnable(第一个线程执行体)
@Override
public void run() {
myList.add("hello world");
}
};
Runnable r2 = new Runnable() {//将匿名内部类赋值给Runnable(第二个线程执行体)
@Override
public void run() {
myList.add("I will come to you world");
}
};
//让两个线程同时跑起来,如下:
Thread t1 = new Thread(r1,"线程A");
Thread t2 = new Thread(r2,"线程B");
t1.start();
t2.start();
try{
t1.join();//启动
t2.join();
}catch(InterruptedException e){
e.printStackTrace();
}
//前部分执行完毕之后,遍历一下数组
String[] strArr = myList.getStrArray();
for(String str : strArr){
System.out.println("str:"+str);
}
}
}
七、线程通信
1.概述
2.等待唤醒机制
线程通信方法
说明 | 方法 |
---|---|
释放锁,进入等待队列 | public final void wait() |
在超过指定的时间前,释放锁,进入等待队列 | public final void wait(long timeout) |
随机唤醒、通知一个线程 | public final void notify() |
唤醒、通知所有线程 | public final void notifyAll() |
八、死锁
多个线程同时被阻塞。
它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。如下图所示,线程 A 持有资源2,线程 B 持有资源1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
示例:
package com.tuling.part8;
public class DeadLock {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
//启动一个线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "拿到了锁1,资源1");
try {
Thread.sleep(4000);//这个睡眠4秒,保证了线程2拿到锁2资源
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "等待锁2,资源2");
synchronized (lock2) { //发生了阻塞
System.out.println(Thread.currentThread().getName() + "拿到了锁2,资源2");
}
}//执行到这里,才能释放锁1资源。
}
}, "线程1").start();
//死锁的线程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "拿到了锁2,资源2");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "等待锁1,资源1");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "拿到了锁1,资源1");
}
}
}
},"线程2").start();
}
}
九、线程池
1.概述
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
和果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在 Java 中可以通过线程池来达到这样的效果。
戋程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源合理利用线程池能够带来三个好处:
1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中了作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
2.线程池的使用
java 里面线程池的顶级接口是 java . util . concurrent . Executor ,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是 java . util . concurrent . ExecutorService 。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java . util . concurrent . Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用 Executors 工程类来创建线程池对象。
Java 类库提供了许多静态方法来创建一个线程池:
Executors 类中创建线程池的方法如下:
newFixedThreadPool 创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
newCachedThreadPoo1创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会増加线程数量;线程池规模无限制。
newSingleThreadPoolExecutor 创建一个单线程的 Executor ,确保任务对了,串行执行
newScheduledThreadPool 创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似 Timer ;
示例:
package com.tuling.part9;
//import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);//睡眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了:"+Thread.currentThread().getName());
System.out.println("教完后,教练回到了游泳池");
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建线程池对象.
//ExecutorService pool = Executors.newFixedThreadPool(3);
//ExecutorService pool = Executors.newCachedThreadPool();
// ExecutorService pool = Executors.newSingleThreadExecutor();
//创建Runnable接口子类对象。(task)
MyRunnable myRunnable = new MyRunnable();
/*
提交Runnable接口子类对象。(take task)
pool.submit(myRunnable);
pool.submit(myRunnable);
pool.submit(myRunnable);
*/
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
for (int i = 0; i < 5; i++) {
scheduledExecutorService.schedule(myRunnable,3, TimeUnit.SECONDS);//每个任务延迟10秒去执行。
}
scheduledExecutorService.shutdown();//关闭线程池不会马上终止程序。
while (scheduledExecutorService.isTerminated()){
}
System.out.println("程序终止");
}
}
3.Callable接口
一般情况下,使用 Runnable 接口、 Thread 实现的线程我们都是法返回结果的。但是如果对一些场合需要线程返回的结果。就要使用用 Callable 、 Future 这几个类 Callable 只能在 ExecutorService 的线程池中跑,但有返回结果,也可以通过返回的 Future 对象查询执行状态。 Future 本身也是一种设计模式,它是用来取得异步任务的结果看看其源码:
public interface Callable < V >{
V cal1() throws Exception ;
}
它只有一个 cal 方法,并且有一个返回 V ,是泛型。可以认为这里返回 V 就是线程返回的结果。
ExecutorService 接口:线程池执行调度框架
< T > Future < T > submit ( Callable < T > task );
< T > Future < T > submit ( Runnable task , T result );
Future <?> submit ( Runnable task );
十、线程安全集合
1.CopyOnWriteArrayList[重点]
线程安全的 ArrayList ,加强版读写分离。写有锁,读无锁,读写之间不阻塞,优于读写锁。写入时,先 copy 一个容器副本、再添加新元素,最后替换引用。使用方式与 ArrayList 无异。
示例:
public class TestCopyOnWriteArrayList {
public static void main ( String [] args ){
//1创建集合
CopyOnWriteArrayList < String > list = new CopyOnWriteArrayList <>();
//2使用多线程操作
ExecutorService es = Executors . newFixedThreadPool (5);
//3提交任务
for ( int i =0; i <5; i ++){
es . submit ( new Runnable (){
@ Override
public void run (){
for ( int j =0; j <10; j ++){
list . add ( Thread . currentThread ()· getName ()+"...."+ new Random (). nextInt (1000));
}
}
});
}
2.CopyOnWriteArraySet
示例:
public class TestCopyOnWriteArrayList {
public static void main ( String [] args ){
//1创建集合
CopyOnWriteArrayList < String > list = new CopyOnWriteArrayList <>();
//2使用多线程操作
ExecutorService es = Executors . newFixedThreadPool (5);
//3提交任务
for ( int i =0; i <5; i ++){
es . submit ( new Runnable (){
@ Override
public void run (){
for ( int j =0; j <10; j ++){
list . add ( Thread . currentThread ()· getName ()+"...."+ new Random (). nextInt (1000));
}
}
}
}
3.ConcurrentHashMap
初始容量默认为16段( Segment ),使用分段锁设计。不对整个 Map 加锁,而是为每个 Segment 加锁。当多个对象存入同一个 Segment 时,才需要互斥。最理想状态为16个对象分别存入16个 Segment ,并行数量16。使用方式与 HashMap 无异。
示例:
public class TestConcurrentHashMap {
public static void main ( String [] args ){
//1创建集合
ConcurrentHashMap < String , String > hashMap = new ConcurrentHashMap < String , String >();
//2使用多线程添加数据
for ( int i =0; i <5; i ++){
new Thread ( new Runnable (){
@ Override
public void run (){
for ( int k =0; k <10; k ++){
hashMap . put ( Thread . currentThread ()· getName ()+"--"+ k , k +""); System . out . println ( hashMap );
}
}
}). start ();
}
}