进程:
一个应用程序就是一个或多个进程
线程:
一个进程有一个或者多个进程
public static void main(String[] args) {
while(true);
}
Thread类
所属包: java.long;
构造方法:
public Thread();
public Thread( String name); //参数是给线程起个名字
public Thread (Runnable target); //传入要执行的Runnable对象
public Thread(Runnable target , String name) ; //可以同时传入中间“ ,”隔开
静态方法:
static Thread currentThread();//获取当前线程
static void sleep(long millis);//休息millis毫秒
//获取当前线程,执行当前代码的线程
Thread c=Thread.currentThread();
String name=c.getName();
System.out.println(name);
成员变量:
private Runnable target;
成员方法:
void strat();//启动线程,执行run方法中代码
void run();//线程执行run中代码
void getName();//获取线程名字
void getState();//获取线程状态
//创建一个子线程
Thread t =new Thread();
//启动线程
t.start();
t.run();
调度方式:
抢占式调度:给每个任务分配时间不等
分时调度:给每个任务分配时间是不均等的
Runnable接口
public abstract void run();
//创建一个Runnable类型对象
Runnable r = new Runnable() {
@Override
public void run() {
String name=Thread.currentThread().getName();
System.out.println(name);
}
};
//把Runnable类型对象,赋值给Thread类的成员变量target
Thread t = new Thread(r,"线程");
t.start();
}
Runnable接口方式和继承Thread类的方式:建议使用Runnable方式
a.线程和任务分离,解耦合,提高代码的健壮性;
b.避免了java单继承的局限性;
c.线程池里面,只能传入Runnable或者callable类对象,不用new Thread
每一个线程启动都会有一个栈,各自在各自的栈中执行任务
线程的开销比一般对象的开销要大
线程安全:
ArrayList<Integer> arr= new ArrayList<>();
//通过匿名内部类的方式创建Runnable类型对象
Runnable ru = new Runnable() {
public void run() {
for (int i = 0; i <10000; i++) {
arr.add(i);
}
System.out.println("=======");
}
};
new Thread(ru).start();
new Thread(ru).start();
Thread.sleep(200);
System.out.println(arr.size());
}
第一次结果:
第二次结果:
算出结果应为20000个,结果却不是20000个,所以出现数据丢失问题
解决线程数据丢失问题(上锁):整个的操作不不是原子操作
1.使用synchronized代码块
语法:
synchronized(锁对象){//可以是任意类型的对象
//写有可能发生线程安全问题的代码
}
static int n = 0;
public static void main(String[] args) throws InterruptedException {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
synchronized (this) {
n++;
}
}
}
};
new Thread(r).start();
new Thread(r).start();
Thread.sleep(3000);
System.out.println(n);
}
2.使用synchronized方法
静态synchronized方法:在static和返回值之间加synchronized关键字
非静态synchronized方法:在返回值之前加synchronized关键字
synchronized代码块和方法:代码执行完毕后,自动释放锁
static int n = 0;
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
//调用synchronized方法
add();
}
}
};
new Thread(r).start();
new Thread(r).start();
Thread.sleep(3000);
System.out.println(n);
}
public static synchronized void add() {
n++;
}
3.使用Lock锁
Lock接口:
void lock();//上锁
void unlock();//开锁
常用实现类: ReentrantLock
构造方法: public ReentrantLock();
static int n = 0;
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
//上锁
lock.lock();
n++;
//解锁
lock.unlock();
}
}
};
new Thread(r).start();
new Thread(r).start();
Thread.sleep(3000);
System.out.println(n);
}
Java中线程状态:
NEW:新建状态。创建了一个线程,启动之前处于该状态
Thread t = new Thread();
State s = t.getState();
System.out.println(s);//NEW
RUNNABLE:可运行状态。线程正在执行任务(run方法中的代码),就处于该状态
Runnable r = new Runnable() {
@Override
public void run() {
for(;;);
}
};
Thread t = new Thread(r);
t.start();
Thread.sleep(100);//100毫秒保证子线程开始执行Thread.sleep(1000000);这一行代码了
//获取并打印线程的状态
System.out.println(t.getState());
}
BLOCKED:阻塞状态。获取synchronized锁对象失败,就处于该状态
Runnable r = new Runnable() {
@Override
public void run() {
synchronized(this) {
for(;;);
}
}
};
Thread t1 = new Thread(r);
t1.start();
Thread t2 = new Thread(r);
t2.start();
Thread.sleep(100);//100毫秒保证两个子线程已经启动,并开始执行代码了
//获取并打印线程的状态
System.out.println(t1.getState());
System.out.println(t2.getState());
//获取了锁对象开始执行for循环的线程处于RUNNABLE状态
//没有获取锁对象的线程处于BLOCKED状态。
}
WAITING:无限等待状态。获取Lock锁对象失败,就处于该状态
Lock lock = new ReentrantLock();
Runnable r = new Runnable() {
@Override
public void run() {
lock.lock();
for(;;);
}
};
Thread t1 = new Thread(r);
t1.start();
Thread t2 = new Thread(r);
t2.start();
Thread.sleep(100);//100毫秒保证两个子线程已经启动,并开始执行代码了
//获取并打印线程的状态
System.out.println(t1.getState());
System.out.println(t2.getState());
//获取了锁对象开始执行for循环的线程处于RUNNABLE状态
//没有获取Lock锁对象的线程处于WAITING状态。
}
TIMED_WAITING:计时等待状态。线程正在执行sleep方法的时候,就处于该状态
Runnable r = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t = new Thread(r);
t.start();
Thread.sleep(100);//100毫秒保证子线程开始执行Thread.sleep(1000000);这一行代码了
//获取并打印线程的状态
System.out.println(t.getState());
}
TERMINATED:消亡状态。线程执行完任务后,就处于该状态
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
Thread t = new Thread(r);
t.start();
Thread.sleep(1000);//1000毫秒子线程一定把任务执行完毕了
//获取并打印线程的状态
System.out.println(t.getState());
}
线程的通信:线程和线程之间沟通
等待唤醒机制:
object类:
void wait();//就会让线程进入等待状态。WAITING状态
void wait(long time);//调用该方法,会让线程进入计时等待状态。TIMED_WAITING
void notify();//调用该方法,会让线程醒来,接着执行任务。
void notifyAll();//调用该方法,会唤醒当前锁对象上等待的所有线程
注意事项:
1.这些方法都必须写在synchronized代码块或者synchronized方法中
2.调用这些方法的对象,必须和锁对象一致。
3. t.notify方法,只能唤醒t锁对象上等待的线程
4. 调用了wait(不论是否有参数)方法后,会自动释放锁对象。
例子:
1.定义一个Fruit水果类
成员变量:stock库存
构造方法、set和get
2.定义一个官网线程类:NetShop,实现Runnable接口
完成卖出水果的动作
3.定义一个实体店线程类:FrontShop,实现Runnable接口
完成卖出水果的动作
4.使用等待唤醒机制完成卖出100份坚果的功能
例如:
官网正在卖出第1份,还剩余99份
实体店正在卖出第2份,还剩余98份
官网正在卖出第3份,还剩余97份
实体店正在卖出第4份,还剩余96份
实体店:
public class FrontShop implements Runnable{
private Fruit f;
public FrontShop(Fruit f) {
super();
this.f = f;
}
@Override
public void run() {
while(true) {
synchronized(f) {
//判断是否退出循环
if(f.getStock()<=0) {
break;
}
//奇数份,实体店卖
if(f.getStock()%2==1) {
//每卖一份,库存减1
f.setStock(f.getStock()-1);
System.out.println("实体店正在卖出第"+(100-f.getStock())+"份,还剩
余"+f.getStock()+"份");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒当前锁对象上等待的线程
f.notify();
}else {
//偶数份,实体店进入等待状态
try {
f.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
官网:
public class NetShop implements Runnable{
private Fruit f;
public NetShop(Fruit f) {
super();
this.f = f;
}
@Override
public void run() {
while(true) {
synchronized(f) {
//判断是否退出循环
if(f.getStock()<=0) {
break;
}
//偶数份,官网卖
if(f.getStock()%2==0) {
//每卖一份,库存减1
f.setStock(f.getStock()-1);
System.out.println("官网正在卖出第"+(100-f.getStock())+"份,还剩余"+f.getStock()+"份");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒当前锁对象上等待的线程
f.notify();
}else {
//奇数份,官网进入等待状态
try {
f.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
水果店:
public class Fruit {
private int stock;
public int getStock() {
return stock;
}
public void setStock(int stock) {
this.stock = stock;
}
}
测试类:
public class Test01 {
public static void main(String[] args) {
//创建Fruit类型的对象
Fruit f = new Fruit();
f.setStock(100);
//创建官网和实体店类型的对象
Runnable r1 = new NetShop(f);
Runnable r2 = new FrontShop(f);
//创建两个线程,分别指向这两个任务
new Thread(r1).start();
new Thread(r2).start();
}
}
结果:
线程池:
1.提高响应速度。预先创建好了线程,只等任务过来执行。
2.降低资源消耗。线程池中的线程,执行完任务后,又返回到线程池中,下一个任务到来后可以继续使用该线程。
3.提高线程的可管理性。一个线程大约需要消耗1M的空间,线程池可以设置最大线程的数量。
Executors类:
static ExecutorService newFiexdThredadPool(int nThread);
ExecutorService接口:
void execute(Runnable r);//执行任务
<T> Future<T> submit(Callable<T> c);
Future<?> submit(Runnable r);
void shutdown();//关闭线程池
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
//创建一个固定线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
Future<?> future = es.submit(r);
Object result = future.get();//null
System.out.println(result);
//关闭线程池
es.shutdown();
}
Future<T>接口
T get();//必须等子线程把任务执行完成,return以后才可以获取返回的结果
Callable<T>接口:
T call();
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
//call方法中的代码是某个子线程执行的
System.out.println(Thread.currentThread().getName());
return null;
}
};
//创建一个固定线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(c);
//关闭线程池
es.shutdown();
}