进程:CPU为每个应用程序分配的独立空间,一个进程可能有多个线程。进程为线程中的摸个执行任务程序,多个进程之间可以进行共享数据。而JAVA的线程则是由JVM进程分配的,main方法所在线程,则成为主线程。
〇、线程状态
正常情况线程执行步骤:新建-》运行-》死亡
当CPU资源不够时,CUP分配给各个线程的资源可能不同(貌似有点像是线程在抢资源,实际是CPU分配资源给每个线程)。
因此就会出现线程的阻塞、休眠、等待3个状态。其中阻塞状态,当cpu资源够时,阻塞状态的线程可能恢复到运行状态,而休眠、等待的线程也可能进入运行状态。休眠、等待状态可能转换成阻塞状态,但是阻塞状态不会变成休眠、等待状态。
一、多线程的实现
0、线程常用方法
package learn.javase.threads;
/**
* 继承Thread创建线程,设置线程名称、获取线程名称
* @author Jole
*
*/
public class ThreadDemo01 {
public static void main(String[] args) {
MyThreadsDemo mt = new MyThreadsDemo();
mt.setName("Hi");
mt.start();
//获取当前线程
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
}
线程实现方式主测试类:
package learn.javase.threads;
public class MyThreadsDemo extends Thread{
public MyThreadsDemo() {
super("Google");
}
public void run() {
for(int i=0;i<5;i++) {
try {
Thread.sleep(1000);
System.out.println(i);
}catch(Exception e) {
System.out.println(e.getMessage());
}
}
}
}
线程测试主类:
package learn.javase.threads;
/**
* 实现多线程的4中方式:
* 1、继承Thread类
* 2、实现Runnable接口
* 3、匿名内部类
* 4、匿名接口
* 5、实现Callable接口,与Runnable的区别:
* 可以有返回值
* 可以抛异常
* @author Jole
*
*/
public class ThreadNumberDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
//方式1,继承Thread
new ThreadThread1().start();
//方式2,实现Runnable接口
new Thread(new ThreadRunnables2()).start();
//方式3,匿名内部类
new Thread() {
public void run() {
System.out.println("匿名内部类,实现多线程");
}
}.start();
//方式4,匿名接口
Runnable r = new Runnable() {
public void run() {
System.out.println("匿名接口,实现多线程");
}
};
new Thread(r).start();
//方式4的简洁版
new Thread(new Runnable() {
public void run() {
System.out.println("匿名接口简洁版,实现多线程");
}
}).start();
}
}
1、继承Thread
package learn.javase.threads;
/**
* 方式一继承Thread实现多线程
* @author Jole
*
*/
public class ThreadThread1 extends Thread{
public void run() {
System.out.println("extends ....thread");
}
}
2、实现Runnable接口
package learn.javase.threads;
/**
* 实现Runnable接口
* @author Jole
*
*/
public class ThreadRunnables2 implements Runnable{
public void run() {
System.out.println("runnable .. threads");
}
}
3、实现Callable
通过使用线程池,实现Callable
4、区别
继承Thread接口与实现Runnable接口,实现多线程区别:
- 1、单继承、多实现
- 2、实现Runnable可以将线程与任务(run())解耦
package learn.javase.threads;
/**
* 实现Runnable接口,创建线程。
* 继承Thread接口与实现Runnable接口,实现多线程区别:
* 1、单继承、多实现
* 2、将线程与任务(run())解耦
* @author Jole
*
*/
public class RunnableDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
new Thread(new RunnableThreadDemo()).start();
for(int i=0;i<5;i++) {
System.out.println("main..."+i);
}
}
}
线程实现类:
package learn.javase.threads;
public class RunnableThreadDemo implements Runnable{
public void run() {
for(int i=0;i<5;i++) {
System.out.println("run..."+i);
}
}
}
二、线程池
1、线程池使用
package learn.javase.threads;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 线程池
* @author Jole
*
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
//使用线程池工场Executors,创建线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//使用线程池,构造器为实现Runnable的接口,使用完后自动放回线程池
es.submit(new ThreadPoolThreas());
es.submit(new ThreadPoolThreas());
//如果线程池里面线程不够了,只有等待其它线程执行完后,在使用
es.submit(new ThreadPoolThreas());
//一般不用关闭线程池,特殊情况关闭线程池,可以使用
// es.shutdown();
}
}
线程池线程实现Runnable接口:
package learn.javase.threads;
public class ThreadPoolThreas implements Runnable{
public void run() {
System.out.println(Thread.currentThread().getName()+"线程池的使用");
}
}
2、线程池-实现多线程
package learn.javase.threads;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 通过线程池,实现Callable接口,实现多线程,并获取任务(run)返回值,和异常
* @author Jole
*
*/
public class ThreadPoolDemo2 {
public static void main(String[] args) throws Exception{
//使用线程池工场Executors创建线程池工场
ExecutorService es = Executors.newFixedThreadPool(2);
//通过实现Callable实现线程,提交并返回Future对象
Future t = es.submit(new ThreadCallableDemo());
//通过get获取返回值
System.out.println(t.get());
}
}
实现类Callable
package learn.javase.threads;
import java.util.concurrent.Callable;
/**
*
* @author Jole
* 使用继承Callable<T>泛型接口,实现多线程,并使用线程池
*/
public class ThreadCallableDemo implements Callable<String>{
public String call() {
return "实现Callable接口";
}
}
3、实例
实例1
package learn.javase.threads;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 使用线程池实现异步提交求和:1+...100= 1+... 200=
* 注意:
* 有返回值
* 计算不同的值
* @author Jole
*
*/
public class ThreadMainDemo1 {
public static void main(String[] args) throws Exception{
//使用线程池工场Executors创建线程池
ExecutorService es = Executors.newFixedThreadPool(3);
//使用线程池中的线程,并使用构造方法传参,求和并返回
Future<Integer> f =es.submit(new CallableDemo(100));
Future<Integer> f2 =es.submit(new CallableDemo(2100));
System.out.println("1+...100="+f.get());
System.out.println("1+...200="+f2.get());
//关闭线程池
es.shutdown();
}
}
实现类:
package learn.javase.threads;
import java.util.concurrent.Callable;
/**
* 通过实现Callable接口,及构造方法传参,并通过call返回值
* @author Jole
*
*/
public class CallableDemo implements Callable<Integer>{
private int number;
//创建构造方法,为了传参
public CallableDemo(int number) {
this.number = number;
}
public Integer call() {
int sum = 0;
//使用构造方法传的参数求和并返回
for(int i=0;i<=number;i++) {
sum=sum+i;
}
return sum;
}
}
实例2:
package learn.javase.threads;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 通过线程池,实现Callable接口,实现多线程,并获取任务(run)返回值,和异常
* @author Jole
*
*/
public class ThreadPoolDemo2 {
public static void main(String[] args) throws Exception{
//使用线程池工场Executors创建线程池工场
ExecutorService es = Executors.newFixedThreadPool(2);
//通过实现Callable实现线程,提交并返回Future对象
Future t = es.submit(new ThreadCallableDemo());
//通过get获取返回值
System.out.println(t.get());
}
}
实现类:
package learn.javase.threads;
import java.util.concurrent.Callable;
/**
*
* @author Jole
* 使用继承Callable<T>泛型接口,实现多线程,并使用线程池
*/
public class ThreadCallableDemo implements Callable<String>{
public String call() {
return "实现Callable接口";
}
}
三、线程安全
1、线程安全
package learn.javase.threads;
/**
* 可能出现安全问题的情况:多线程,共享一个数据
* 线程安全问题,及解决办法加synchronized,实例:卖票
* @author Jole
* synchronized解决线程安全问题,使用公式:
* synchronized(锁(任意对象)){
* 代码块
* }
*
* 注意:因为加了锁,所以效率会降低,但是安全性得到了保证,效率低的原因如下:
* 1、每次线程都会先判断是否有锁
* 2、获取锁
* 3、执行完代码块
* 4、执行完后,还会锁
* 5、在等下一个锁执行相同操作,相当于只能排队上一个厕所。
*/
public class ThreadSeaerfDemo {
public static void main(String[] args) {
//多线程出现的安全问题,票有负数还在卖
// Tickets00 t = new Tickets00();
// Thread t0 = new Thread(t);
// Thread t1 = new Thread(t);
// Thread t2 = new Thread(t);
// t0.start();t1.start();t2.start();
//原始代码
// Tickets01 t = new Tickets01();
// Thread t0 = new Thread(t);
// Thread t1 = new Thread(t);
// Thread t2 = new Thread(t);
// t0.start();t1.start();t2.start();
//第一次优化代码:同步方法,同步锁对象为:this
// Tickets02 t = new Tickets02();
// Thread t0 = new Thread(t);
// Thread t1 = new Thread(t);
// Thread t2 = new Thread(t);
// t0.start();t1.start();t2.start();
//第一次优化代码:同步静态方法,同步锁对象为:类名.class
Tickets03 t = new Tickets03();
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();t1.start();t2.start();
}
}
多线线程出现的问题
package learn.javase.threads;
/** 〇、原始多线程,会出现安全问题,会出现票卖完了还在卖,也就是会出现票为负数的情况
* @author Jole
*
*/
public class Tickets00 implements Runnable{
//票的数量
private int ticket = 100;
public void run() {
while(true) {
if(ticket>0) {
//为了展示出现线程安全问题,此处让线程修改20毫秒
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每次票减1
System.out.println(Thread.currentThread().getName()+ " " + ticket--);
}
}
}
}
2、线程安全解决方案
解决多线程问题方案使用同步锁,1、使用synchronized 2、使用Lock接口
同步锁实现原理图:
1、synchronized
使用synchronized解决线程安全问题,使用公式:
synchronized解决线程安全问题,使用公式:
synchronized(锁(任意对象)){
代码块
}
1.1、synchronized加在外面:同步锁对象为obj
package learn.javase.threads;
/** 一、原始线程同步解决,同步锁为obj
*
* synchronized解决线程安全问题,使用公式:
* synchronized(锁(任意对象)){
* 代码块
* }
* @author Jole
*
*/
public class Tickets01 implements Runnable{
//票的数量
private int ticket = 100;
Object obj = new Object();
public void run() {
while(true) {
//为了解决安全问题加入了synchronized关键字
synchronized (obj) {
if(ticket>0) {
//为了展示出现线程安全问题,此处让线程修改20毫秒
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//每次票减1
System.out.println(Thread.currentThread().getName()+ " " + ticket--);
}
}
}
}
}
1.2、synchronized加在非静态方法上:此时同步锁对象为this
package learn.javase.threads;
/**
* 二、优化原始的线程安全解决办法,同步锁方法,同步锁对象为this
* @author Jole
*
*/
public class Tickets02 implements Runnable{
//总票数
private int tickets =100;
//原始同步锁
// Object obj = new Object();
public void run() {
bay();
}
//第一次优化,将锁和代码块抽为一个方法,测试同步锁为obj
// public void bay() {
// while(true) {
// synchronized (obj) {
// if(tickets > 0) {
// try {
// Thread.sleep(20);
// }catch(Exception e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+" " +tickets-- );
// }
// }
// }
// }
//第二次进一步优化为,同步方法,此时同步锁为:this
//测试就可以省去以前的obj对象的创建了,代码以前更占用少一点内存了
public synchronized void bay() {
while(true) {
if(tickets > 0) {
try {
Thread.sleep(20);
}catch(Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" " + tickets--);
}
}
}
}
1.3、synchronized加在静态方法上:此时同步锁对象为:类名.class
package learn.javase.threads;
/**
* 静态同步方法,此时同步锁为:类名.class
* 如此类中的同步锁对象是:Tickets03.class
* @author Jole
*
*/
public class Tickets03 implements Runnable{
//总票数
private static int tickets =100;
public void run() {
buy();
}
//静态方法同步锁,此时代码的锁为:Tickets03.class
public static synchronized void buy() {
while(true) {
if(tickets > 0) {
try {
Thread.sleep(20);
}catch(Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" " + tickets--);
}
}
}
}
2、Lock
使用synchronized时,获取锁,释放锁都是JVM自动完成的,而通过Lock可以手动获取锁,释放锁。
package learn.javase.threads;
/**
* 手动获取锁,释放锁
* @author Jole
*
*/
public class ThreadShouDong {
public static void main(String[] args) {
ThreadRunnableDemo01 t = new ThreadRunnableDemo01();
Thread t0 = new Thread(t);
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t0.start();
t1.start();
t2.start();
}
}
Lock接口实现类:
package learn.javase.threads;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 手动获取锁,释放锁
* @author Jole
*
*/
public class ThreadRunnableDemo01 implements Runnable{
//票总数
private int tickets = 100;
//锁 Lock接口的实现类ReentrantLock
private Lock lock = new ReentrantLock();
public void run() {
while(true) {
//获取锁
lock.lock();
try {
if(tickets > 0) {
Thread.sleep(20);
System.out.println(Thread.currentThread().getName()+" "+tickets--);
}
}catch(Exception e) {
e.printStackTrace();
}finally {
//释放锁
lock.unlock();
}
}
}
}
3、注意点
使用多线程为了保证数据的安全性,加了synchronized或Lock保持同步,因此效率上就降低了。
可能出现安全问题的情况:多线程,共享一个数据。
使用synchronized时,同步锁必须保证为同一个同步锁对象才行。
四、死锁
死锁:多个线程,对方互相等待获取对方的锁。双方一直处于等待获取对方锁对象状态,也就是死锁。
package learn.javase.threads;
/**
* 死锁:多个线程,对方互相等待获取对方的锁。双方一直处于等待获取对方锁对象状态,也就是死锁。
* @author Jole
*
*/
public class DealThreadMainDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
DetalThread death = new DetalThread();
Thread t0 = new Thread(death);
Thread t1 = new Thread(death);
t0.start();
t1.start();
}
}
同步锁对象A:
package learn.javase.threads;
/**
* 同步锁A,对象
* @author Jole
*
*/
public class LockA {
private LockA() {
};
public static final LockA lockA = new LockA();
}
同步锁对象B:
package learn.javase.threads;
/**
* 同步锁B,对象
* @author Jole
*
*/
public class LockB {
public LockB() {
}
public static final LockB lockB = new LockB();
}
模拟出现死锁情况,实现类:
package learn.javase.threads;
public class DetalThread implements Runnable{
private int number = 0;
public void run() {
while(true) {
if(number % 2 ==0) {
synchronized (LockA.lockA) {
System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"if抢到资源--获取到--同步锁A对象");
synchronized (LockB.lockB) {
System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"if抢到资源--获取到--同步锁B对象");
}
}
}else {
synchronized (LockB.lockB) {
System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"else抢到资源--获取到--同步锁B对象");
synchronized (LockA.lockA) {
System.out.println(Thread.currentThread().getName()+"线程,"+"第"+number+"次,"+"else抢到资源--获取到--同步锁A对象");
}
}
}
number++;
}
}
}
注意获取的锁是那个。
五、线程等待、唤醒
实例:input拷贝数据对象,output输入对象:如果不是用线程等待与唤醒,出现问题:拷贝的数据太快,还来不及输出,可能出现输出的数据错乱。使用线程等待和唤醒,可以控制拷贝一个后,拷贝线程等待,然后输出线程输出,输出后,输出线程等待,然后拷贝线程唤醒,进行拷贝数据,依次循环。从而保证拷贝一个数据,输出一个数据。
package learn.javase.threads;
/**
* 线程等待、唤醒:
* 涉及同步锁是否是同一个锁,只有锁调用wait和notify才有效,不然抛出异常。
* 正常情况是交替出现,不会出现数据错乱情况。
* @author Jole
* 本类是主测试类
* 该实例:input拷贝数据对象,output输入对象:
* input可能出现拷贝对象拷贝很快,output输出对象很慢,导致output输出数据错乱,因此只能单个拷贝输出,且每次拷贝完后需wait等待,然后等输出完成后在拷贝。
* 因此需要拷贝完成后,等待,并唤醒输出,等输出完成前唤醒拷贝,然后自己在等待,然后拷贝又拷贝,然后又唤醒输出,如此循环。
* 实现方法加一个flag标记,是该拷贝还是输出
*/
public class WatiNotifyThreadMainDemo {
public static void main(String[] args) {
//操作的是同一个对象锁,不然拷贝和输出都是用自己的锁this,也会发生数据错乱
ResourceData resource = new ResourceData();
//保证是同一个锁对象,所以都写了构造方法传入同一个锁对象
InputDemo input = new InputDemo(resource);
OutputDemo output = new OutputDemo(resource);
Thread t0 = new Thread(input);
Thread t1 = new Thread(output);
t0.start();
t1.start();
}
}
要拷贝的数据类:
package learn.javase.threads;
/**
* 操作数据
* @author Jole
*
*/
public class ResourceData {
public String name;
public String sex;
public boolean flag;
}
拷贝线程:
package learn.javase.threads;
/**
* 拷贝线程:
* 当标记flag=true时,拷贝完成,需等待
* @author Jole
*
*/
public class InputDemo implements Runnable {
private ResourceData resourceData;
public InputDemo(ResourceData resourceData) {
this.resourceData = resourceData;
}
@Override
public void run() {
int number=0;
while(true) {
synchronized (resourceData) {
//如果为ture拷贝完成,需等待
if(resourceData.flag) {
try {
resourceData.wait();
}catch(Exception e) {
e.printStackTrace();
}
}
//拷贝值,比如写死拷贝张三,30岁,lishi,20岁
if(number % 2==0) {
resourceData.name = "张三";
resourceData.sex = "男";
}else {
resourceData.name = "Lishi";
resourceData.sex = "nv";
}
//设置true,以便下次进入等待wait
resourceData.flag = true;
//唤醒输出线程
resourceData.notify();
}
number++;
}
}
}
输出线程:
package learn.javase.threads;
/**
* 输出线程:
* 当标记flag=false时,输出完成,需等待
* @author Jole
*
*/
public class OutputDemo implements Runnable {
private ResourceData resourceData;
public OutputDemo(ResourceData resourceData) {
this.resourceData = resourceData;
}
@Override
public void run() {
while(true) {
synchronized (resourceData) {
//如果为false,则输出完成,等待
if(!resourceData.flag) {
try {
resourceData.wait();
}catch(Exception e) {
e.printStackTrace();
}
}
//否则输出,并唤醒拷贝
System.out.println(resourceData.name+" .. "+ resourceData.sex);
resourceData.flag = false;
resourceData.notify();
}
}
}
}
评论 (0)