1.进程和线程的关系
进程是多个线程的集合,进程是正在执行的程序,线程是进程中独立运行的子任务,比如正在执行的QQ就是一个进程,里面的好友视频,下载文件,传输数据等等就是独立的子任务,也就是线程。
每一个进程中都有一个主线程,JAVA代码中,main 函数就是一个主线程,多线程可以提高程序的运行效率,任务可以不用排队等待。
1.1 创建线程的方式
1.1.1 继承Thread类的方式
1.在类上继承 Thread
2.在类中重写 Thread的 run 方法
3.在 run 方法里写 线程需要执行的代码
4. 实例化 该类的 对象,调用 start 方法
public class Thread01 {
/**
* 什么是进程:进程就是一个正在执行的程序
* 什么是线程:线程是进程中独立执行的子任务
*/
public static void main(String[] args) {
CreateThread createThread = new CreateThread();
createThread.start();
}
}
class CreateThread extends Thread{
public void run() {
// TODO Auto-generated method stub
// 买出30张火车票
for(int i=1;i<=30;i++)
{
System.out.println("第"+i+"张票买出");
}
}
}
1.1.2 实现runnable接口方式
1.在类上实现 Runnable 接口
2.重写Runnbale的 run 方法
3.在 run 方法里写 线程需要执行的代码
4. 把该类对象添加到 thread 对象 的构造方法中
5. 调用thread对象的 start 方法
public class Runnable01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t = new Thread(new CreateRunnable());
t.start();
}
}
class CreateRunnable implements Runnable {
public void run() {
// TODO Auto-generated method stub
// 买出30张火车票
for(int i=1;i<=30;i++)
{
System.out.println("第"+i+"张票买出");
}
}
}
注:使用接口方式创建线程比 使用 继承方式更好,因为开发都是面向接口编程,并且继承是单一的,而接口是可以多实现的。
1.1.3 使用匿名内部类方式
1.把 Runnbale 对象 加入 到 Thread对象的构造方法中
2.重写 run 方法
3.调用 thread 的 start 方法
public class Thread02 {
/**
* 匿名 内部类 创建线程
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
// 买出30张火车票
for(int i=1;i<=30;i++)
{
System.out.println("第"+i+"张票买出");
}
}
}).start();
}
}
1.1.4 使用线程池创建方式
1.2 线程常用的API
1. getID() 获取当前线程的ID ,Runnable和匿名 内部类都没有此方法
2. Thread.sleep(1000) 让当前线程休眠1秒后进入就绪态
放弃线程对CPU一秒钟的资源竞争
3.Thread.currentThread() 获取到当前线程
Thread.currentThread().getID() 获取当前线程的 ID
public class Thread03 {
/**
* 什么是进程:进程就是一个正在执行的程序
* 什么是线程:线程是进程中独立执行的子任务
*/
public static void main(String[] args) {
//Thread.currentThread() 和 getID()
System.out.println(Thread.currentThread().getId());
CreateThread03 createThread = new CreateThread03();
createThread.start();
}
}
class CreateThread03 extends Thread{
//1. getID 获取当前线程的ID ,Runnable和匿名 内部类都没有此方法
public void run() {
// TODO Auto-generated method stub
// 买出30张火车票
for(int i=1;i<=30;i++)
{
try {
// Slepp()
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//getID()
System.out.println(getId()+"第"+i+"张票买出");
}
}
}
1.3 守护线程和非守护线程(用户线程)
守护线程是用于服务其他线程的,相当于非守护线程的保姆,垃圾回收器就是一个守护线程。
与主线程的关系
用户线程是主线程 main 创建的线程,但主线程 main 死掉或者执行完毕,不会影响到用户线程。
当主线程 main 死掉或者执行完毕,守护线程和主线程 一起销毁。
把用户线程设置为守护线程方式
userthread.setDaemon(true);
举列
public class Thread04 {
public static void main(String[] args){
Thread t = new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
for(int i =1 ; i<=30;i++)
{
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("子线程ID:"+Thread.currentThread().getId()+"次数:"+i);
}
}
});
//把该线程t 设置成守护线程,可以发现主线程结束后,守护线程也结束了
t.setDaemon(true);
t.start();
for(int i =1 ; i<=5;i++)
{
System.out.println("主线程ID:"+Thread.currentThread().getId()+"次数:"+i);
}
System.out.println("主线程执行完毕,用户线程进行执行");
}
}
1.4多线程运行的状态
新建状态
就绪状态
运行状态
堵塞状态
死亡状态
1.4.1 Join()方法
Join() 方法的作用:让其他线程编程阻塞状态
B.join();
如:添加在A线程中,A变成阻塞状态,直到B线程执行完毕和释放,A才执行。
2.多线程实现同步和安全
2.1 什么是线程安全
当多个线程共享同一个全局变量时,在对该变量进行写操作时,可能会产生数据冲突和不确定问题,也就是线程安全问题,读操作不会发生线程安全问题,因为归根到底读操作是受到写操作影响。
2.2多线程同步方法
2.2.1 使用synchronized锁
同步代码块方式,用synchronized包裹共享代码块,且使用同一把锁。
public class Thread04 {
public static void main(String[] args){
CreaterThreadx createrThreadx = new CreaterThreadx();
//t1 t2 两个线程同时对 tk变量进行操作。
Thread t1 = new Thread(createrThreadx);
Thread t2 = new Thread(createrThreadx);
t1.start();
t2.start();
}
}
class CreaterThreadx implements Runnable{
// tk为共享变量
private int tk = 100;
Object object = new Object();
public void run() {
// TODO Auto-generated method stub
while(tk>0)
{
sale();
}
}
public void sale(){
//同步代码块方式
synchronized (object) {
if(tk>0)
{
System.out.println("线程"+Thread.currentThread().getId()+"出售第"+(100-tk+1));
tk--;
}
}
}
}
当同步代码块执行完毕或者抛出异常,则当前线程就会释放锁。
同步函数方式 在方法上加上synchronized,使用的this锁
public class Thread04 {
public static void main(String[] args){
CreaterThreadx createrThreadx = new CreaterThreadx();
//t1 t2 两个线程同时对 tk变量进行操作。
Thread t1 = new Thread(createrThreadx);
Thread t2 = new Thread(createrThreadx);
t1.start();
t2.start();
}
}
class CreaterThreadx implements Runnable{
// tk为共享变量
private int tk = 100;
Object object = new Object();
public void run() {
// TODO Auto-generated method stub
while(tk>0)
{
sale();
}
}
public synchronized void sale(){
//同步代码块方式
/* synchronized (object) {*/
if(tk>0)
{
System.out.println("线程"+Thread.currentThread().getId()+"出售第"+(100-tk+1));
tk--;
}
/*}*/
}
}
静态同步函数 不是使用this锁,采用 .class文件,当前字节码文件。
2.2.2 多线程死锁
一般来说都是 锁中嵌套锁,导致锁获取不到,资源也释放不了。
public class Thread05 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
final Object object1 = new Object();
final Object object2 = new Object();
new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
synchronized (object1) {
System.out.println("o1上锁,等待o2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (object2) {
System.out.println("t1执行完毕");
}
}
}
}).start();
new Thread(new Runnable() {
public void run() {
// TODO Auto-generated method stub
synchronized (object2) {
System.out.println("o2上锁,等待o1");
synchronized (object1) {
System.out.println("t2执行完毕");
}
}
}
}).start();
}
}
3.JAVA内存模型(多线程的关系)
多线程三大特性:原子性,有序性,可见性
原子性: 代码块要不不执行,要不一起执行。原子操作。
有序性:代码块按照从上到下顺序执行。
可见性:共享变量的变化,可以被其他方法获取到。
JAVA内存模型由 主内存(存放共享全局变量)和本地内存(存放私有变量)组成。
本地内存会复制主内存的变量到本地内存中,线程会对本地内存的共享变量
进行写操作,然后更新到主内存中,然而其他本地内存中的共享变量没有与主
内存一起更新,就导致其他线程使用的还是以前的共享变量,这就是可见性问题。
Volatile 解决可见性问题
Volatile 可以解决可见性问题,但不能解决原子性问题
AtomicInteger 解决原子性问题
在JDK1.5的Atomic 包下 ,可以保证int String boolean 等类型的原子性操作。
用法如下:
public class Thread06{
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadAtomic[] ThreadAtomic = new ThreadAtomic[10];
for(int i = 0 ;i< ThreadAtomic.length;i++)
{
ThreadAtomic[i] = new ThreadAtomic();
}
for(int i = 0 ;i< ThreadAtomic.length;i++)
{
ThreadAtomic[i].start();
}
}
}
class ThreadAtomic extends Thread {
//private static int count = 0; 相当于以下。
private static AtomicInteger count = new AtomicInteger(0);
public void run() {
for(int i=0;i<1000;i++)
{
//count++;
count.incrementAndGet();
}
//输出count
System.out.println(getId()+"次数"+count.get());
}
}
4.多线程通讯
多线程通讯就是 多个线程操作同一个资源,但是操作的动作不同。
案列操作:
一个共享资源:User对象
一个生产者线程:InputThread
一个消费者线程:Outinpu tThread
生产者线程进行写操作,消费者线程进行读操作。
public class User {
private String name;
private String Sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return Sex;
}
public void setSex(String sex) {
Sex = sex;
}
}
生产者线程
public class InputThread extends Thread {
User user;
public InputThread(User user){
this.user= user;
}
public void run() {
// TODO Auto-generated method stub
int count = 0;
while(true)
{
count=(count+1)%2;
//count是偶数时就执行这个
if(count ==0)
{
user.setName("小明");
user.setSex("男");
}
else
{
user.setName("小红");
user.setSex("女");
}
}
}
}
消费者线程
public class OutinputThread extends Thread {
User user;
public OutinputThread(User user){
this.user= user;
}
public void run() {
while(true)
{
System.out.println(user.getName()+","+user.getSex());
}
}
}
测试类
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
User user = new User();
InputThread inputThread = new InputThread(user);
OutinputThread outinputThread = new OutinputThread(user);
}
}
结果:
出现了线程不安全,结果不确定现象。
原因分析: 生产者线程和消费者线程出现了非原子性的操作,生产者线程的代码块还没有操作完毕,消费者线程就开始进行了读取操作,所以导致了结果不确定现象。
解决办法:
1.给共享资源加锁,保证同步读写(略)。
2.采用wait和notify方法 ,只能在synchronized中使用。
wait()方法
释放掉当前线程所持有的锁
让当前线程从运行状态,变成阻塞状态
notify()方法
获取当前线程所持有的锁
让当前线程从阻塞状态,变成运行状态(不是就绪态)
以上两个方法在多个线程同步的时候才能使用。
生产者线程
public class InputThread extends Thread {
User user;
int count = 0;
public InputThread(User user){
this.user= user;
}
public void run() {
// TODO Auto-generated method stub
while(true)
{
count=(count+1)%2;
//count是偶数时就执行这个
synchronized (user) {
if(Test.flag==1)
{
try {
//让当前线程变成阻塞态。并释放掉user这个锁资源
user.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(count ==0)
{
user.setName("小明");
user.setSex("男");
}
else
{
user.setName("小红");
user.setSex("女");
}
Test.flag=1;
//让当前休眠的线程变成运行态(不是就绪),并获取user这个锁资源 user
user.notify();
}
}
}
}
消费者线程
public class OutinputThread extends Thread {
User user;
public OutinputThread(User user){
this.user= user;
}
public void run() {
while(true)
{
synchronized (user) {
if(Test.flag==0)
{
try {
//让当前线程变成阻塞态。并释放掉user这个锁资源
user.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(user.getName()+","+user.getSex());
Test.flag=0;
//让当前休眠的线程变成运行态(不是就绪),并获取user这个锁资源
user.notify();
}
}
}
}
测试类
public class Test {
static int flag =0;
public static void main(String[] args) {
// TODO Auto-generated method stub
User user = new User();
InputThread inputThread = new InputThread(user);
OutinputThread outinputThread = new OutinputThread(user);
inputThread.start();
outinputThread.start();
}
}
结果如下:
可以发现,解决了线程不安全的现象 1.生产者和消费者同步了,2.解决了原子性问题。
4.1 Lock锁
是一把可以自定义开始和结束的锁
Lock和synchronized的区别
1.synchronized发生异常时会自动释放锁,Lock在发生异常时,必须把unLock()方法加入到finally中
2.Lock可以让正在等待获取锁的线程中断,让此线程可以干别的事,而synchronized不能释放正在等待锁资源的线程。
3.Lock锁可以以自旋的方式去获取锁,是非阻塞的方式,synchronized是阻塞的方式
Lock的 await() 方法
相当于 synchronized的 wait()方法
lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
conditon.await();
Lock的 signal() 方法
相当于synchronized的 notify() 方法
lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
conditon.signal();
Lock 的上锁和 解锁方式
注:一定要配备try catch finally使用,否则当异常时无法释放锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Thread07 {
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Thread t1 = new Thread(threadLock);
Thread t2 = new Thread(threadLock);
t1.start();
t2.start();
}
}
class ThreadLock implements Runnable
{
Lock lock = new ReentrantLock();
int count = 30;
public void run() {
while(count>0)
{
//LOCK锁要配和try catch finally使用,否则出现异常,锁不能释放
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"票数:"+count);
count--;
}
catch (Exception e){
}
finally {
lock.unlock();
}
}
}
}
ThreadLocal 本地线程
把主内存的共享变量复制下来,为每一个线程提供一个局部变量,
每一个线程都可以独立的改变自己的变量,而不会影响到其他线程的变量。
Vector和ArraryList的区别
Vector是线程安全的,效率不高,方法上加了synchronized锁
ArraryList是线程不安全的,效率高
HashTable,HashMap,concurrentHashMap的区别
HashTable 是线程安全的,效率最低,方法上加了synchronized锁
HashMap不是线程安全的,但效率最高
conrurentHashMap是线程安全的,效率比HashTable高,采用分段锁
conrurentHashMap将一个整体拆分成多个小的HashTable,默认分成16段。
并发队列
都继承 Queue接口
ConcurrentLinkedQueue 高性能队列
底层采用链表的结构实现,是一种无界安全队列,性能好于BlockingQueue,不会出现队列阻塞的情况,因为链表可以无限开辟。
BlockingQueue 阻塞队列
当队列满的时候,就会变成阻塞状态,是一种有界安全队列。
使用ConcurrentLinkedQueue 的例子
实现生产者生成一个数据,消费者就获取一个数据
public class Thread08 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ConcurrentLinkedQueue<String> concurrentLinkedQueue = new ConcurrentLinkedQueue<String>();
new ProdectorThread(concurrentLinkedQueue).start();
new ConsumerThread(concurrentLinkedQueue).start();
}
}
class ProdectorThread extends Thread
{
// 创建队列
private ConcurrentLinkedQueue concurrentLinkedQueue;
//创建标志位
private volatile Boolean flag= true;
//创建atomic 的全局静态变量,保证原子性
private static AtomicInteger count = new AtomicInteger();
public ProdectorThread(ConcurrentLinkedQueue concurrentLinkedQueue){
this.concurrentLinkedQueue=concurrentLinkedQueue;
}
@Override
public void run() {
System.out.println("生产者线程启动。。。。。。");
try {
while(flag)
{
//这里sleep的作用是,让消费者先执行,进入wait状态。
Thread.sleep(1000);
synchronized (concurrentLinkedQueue) {
System.out.println("正在添加队列");
//数据自增
String date = count.incrementAndGet()+"";
//数据入队列
concurrentLinkedQueue.offer(date);
System.out.println("添加队列成功");
concurrentLinkedQueue.notify();
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
System.out.println("生产者停止。。。。");
}
}
public void stopThread(){
this.flag = false;
}
}
class ConsumerThread extends Thread{
// 创建队列
private ConcurrentLinkedQueue concurrentLinkedQueue;
//创建标志位
private volatile Boolean flag= true;
public ConsumerThread(ConcurrentLinkedQueue concurrentLinkedQueue)
{
this.concurrentLinkedQueue=concurrentLinkedQueue;
}
@Override
public void run() {
System.out.println("消费者线程启动");
try {
while(flag)
{
synchronized (concurrentLinkedQueue) {
//出队列
String date=(String) concurrentLinkedQueue.poll();
if(date!=null)
{
System.out.println("消费者获取date"+date);
}
else
{
concurrentLinkedQueue.wait();
}
}
}
} catch (Exception e) {
// TODO: handle exception
}finally{
System.out.println("消费者停止");
}
}
}
线程池
一个项目中经常创建,启动,销毁线程非常消耗资源和时间的,线程池可以对线程进行复用和管理。
线程池可以复用线程,从而做到节约资源和时间,也方便对线程进行管理。
线程池核心使用的方法是 ThreadPoolExecutor,接口是Executors
Executors提供创建4种线程池的方法
1.newCachedThreadPool方法是一个可缓存线程池,如果线程池长度超过处理需要,则可以灵活的回收空闲线程
2.newFixedThreadPool方法 是一个定长线程池,可以控制线程的最大并发输,超出的线程将会在队列中等待
3.newScheduledThreadPool方法 是一个定长线程池,支持定时和周期性任务
4.newSingleThreadPool方法是一个单线程化的线程池,保证任务按照顺序执行
newCachedThreadPool的使用
public class Thread09 {
public static void main(String[] args){
//1.创建可缓存的线程池
ExecutorService newcached= Executors.newCachedThreadPool();
//2.通过线程池创建9个线程
for(int i = 0; i<9;i++)
{
final int temp=i;
//3.调用execute方法,实现Runnable接口创建线程。
newcached.execute(new Runnable() {
public void run() {
// TODO Auto-generated method stub
System.out.println("线程名称"+Thread.currentThread().getName()+" i:"+temp);
}
});
}
}
}
结果:
可以发现线程池pool-1,没有创建9个线程,只创建了6个线程
证明了线程池已经对线程进行了复用,从而提高了效率
newFixedThreadPool的使用
public class Thread10 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//1.可固定长度的线程池,最多有4个线程
ExecutorService newFixed= Executors.newFixedThreadPool(4);
for(int i=0;i<10;i++)
{
final int temp=i;
newFixed.execute(new Runnable() {
public void run() {
// TODO Auto-generated method stub
System.out.println("线程名称"+Thread.currentThread().getName()+" i:"+temp);
}
});
}
}
}
总结:
可以发现10次new Runnable,但一共只创建了规定的4个线程,
实现了线程池的固定长度和线程池的复用。
newScheduledThreadPool的使用
newScheduledThreadPool主要采用schedule方法进行线程定时的设置,规定线程在规定的时间内启动。
如规定线程在3秒以后全部启动。
public class Thread11 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//1.可定时的线程池,设置线程池最多有4个线程。
ScheduledExecutorService newSched= Executors.newScheduledThreadPool(4);
for(int i=0;i<10;i++)
{
final int temp=i;
//2.采用schedle方法进行定时设置,表示3秒后线程全部开始启动
newSched.schedule(new Runnable() {
public void run() {
// TODO Auto-generated method stub
System.out.println("线程名称"+Thread.currentThread().getName()+" i:"+temp);
}
},3,TimeUnit.SECONDS);
}
}
}
总结:
可以发现该线程池也可以设置线程池的最大线程数
该线程池也可以设置线程启动的时间和周期
shutdown 方法 停止线程池。
线程池原理
线程池都是采用的 new ThreadPoolExector() 构造函数创建的。
核心线程池是指初始化时线程池的数量。
最大线程池是指线程池中线程最大的数量。
ThreadPoolExector的参数
1.CorePoolSize 设置核心池大小
2.MaximumPoolSize 设置线程池最大线程数
3.KeepAliveTime 设置线程存活的时间
4.Unit 设置存活时间的单位
具体流程