线程 程序:为解决某种问题,使用计算机语言编写的一系列指令(代码)的集合. 本章中的程序,特指的是静态的,安装在硬盘上的代码集合. 进程:运行中的程序(被加载到内存中),是操作系统进行资源分配的最小单位 线程:进程可以进一步细化为线程,是进程内最小的执行单元, 是cpu进行任务调度的最小单位 运行中的QQ就是一个进程,操作系统会为这个进程分配内存资源, 一个聊天窗口就认为是一个线程,这多个聊天窗口可以同时被cpu执行 但是这些聊天窗口属于进程 早期没有线程,早期cpu执行的时候,是以进程为单位执行 进程单位还是比较大的,当一个进程运行时,其他的进程就不能执行, 所以后来,将进程中的多个任务,细化为线程 cpu执行单位,也从进程转为更小的线程 进程和线程的关系 一个进程中可以包含多个线程,一个线程只能隶属于一个进程, 线程不能有力进程存在 一个进程中至少有一个线程 java中的main方法,就是用来启动主线程 在主线程中可以创建并启动其他线程,所有线程都共享进程的内存资源 需求:想在java程序中有几件不相关的事情同时有机会执行 可以在java中创建线程,把一些要执行的任务放在线程执行
创建线程
●
继承Thread类的方式
●
实现Runnable接口的方式
创建方式1: 继承Thread类的方式,重写run() 创建自己定义的线程类对象,调用start()启动线程 MyThead extends Thread{ } 创建方式2:实现Runable接口方式 类 实现Runable接口,把这个类不能成为线程,是一个人任务类 重写run() new Thead(任务对象); 调用线程对象的start(),启动线程 以后用第二种方式多一些: 1.避免单继承的局限性,因为java是单继承的,继承了Thread类,就不能继承其他类了 2.更适合多线程共享同一份资源的场景
package day16thread.demo1;
/*
创建线程方式1:
Mythead 继承 Thead(继承)
重写Thead类中的run()方法, 在run()方法中编写我们需要执行的代码
不能直接调用run()方法
*/
public class Mythead extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("Mythead"+i);
}
}
}
package day16thread.demo1;
public class Test {
public static void main(String[] args) {
Mythead mythead=new Mythead();
//mythead.run();//普通方法调用,不是启动线程
mythead.start();//启动线程,并不是会立即执行,需要操作系统的调度
for(int i=0;i<1000;i++){
System.out.println("main"+i);
}
}
}
package day16thread.demo2;
/*
创建一个任务,实现Runable接口
重写Runable中的run()方法
*/
public class MyTask implements Runnable{
@Override
public void run() {
Thread thread=Thread.currentThread();//获得当前正在执行的线程
System.out.println(thread.getName());
/*for(int i=0;i<10000;i++){
System.out.println("mytask"+i);
}*/
}
}
package day16thread.demo2;
public class Test {
public static void main(String[] args) {
MyTask myTask=new MyTask();
Thread t1=new Thread(myTask);
t1.setName("我的线程1");
t1.start();
Thread t2=new Thread(myTask);
t2.setName("我的线程2");
t2.start();
/*for(int i=0;i<10000;i++){
System.out.println("main"+i);
}*/
}
}
Thread类常用的方法 run(); start(); 线程启动 构造方法 new Thread(Runable runable),接收一个任务对象 new Thread(Runable runable,String name),接收一个任务对象,并为线程设置名字 setName();为线程设置名字 String getName();获得线程的名字 Thread.currentThread();获得当前正在执行的线程 setPriority();设置优先级 1-10之间,默认是5 getPriority();获取优先级 long sleep();让线程休眠一段时间 join();让其他线程等待当前线程结束
Thread类有如下3个静态常量来表示优先级
●
MAX_PRIORITY:取值为10,表示最高优先级。
●
MIN_PRIORITY:取值为1,表示最底优先级。
●
NORM_PRIORITY:取值为5,表示默认的优先级
package day16thread.demo3;
public class MyTask implements Runnable{
public void run(){
Thread thread=Thread.currentThread();
if(thread.getName().equals("线程1")){
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(thread.getName()+":"+thread.getPriority());
}
}
package day16thread.demo3;
public class Test {
public static void main(String[] args) throws InterruptedException {
MyTask myTask=new MyTask();
Thread t1=new Thread(myTask,"线程1");
Thread t2=new Thread(myTask,"线程2");
t1.setPriority(6);//设置优先级,1-10之间,默认是5,
t1.start();
t1.join();//让其他线程等待当前线程
t2.start();
System.out.println(Thread.currentThread().getPriority());
}
}
线程生命周期,线程从创建到销毁期间经理5个状态 新建 new Thread();此状态还不能被执行 调用start()启动线程,让线程进入到就绪状态 就绪 当获得CPU执行权后,线程进入到CPU执行 运行 运行中的线程可以被切换,又回到就绪状态,也可能因为休眠等原因进入到阻塞状态 阻塞 线程休眠时间到了,回到就绪状态 死亡 当线程中所有任务执行完了,自动销毁 守护线程 守护线程也是线程的一种,区别在于他的结束 如果一个线程是守护线程,那么他会等java中其他线程任务结束后,自动终止 守护线程为其他线程提供服务,例如JVM中的垃圾回收线程
package day16thread.demo4;
public class Task implements Runnable{
public void run(){
while(true){
System.out.println("我是守护线程,我为大家服务");
}
}
}
package day16thread.demo4;
public class Test {
public static void main(String[] args) {
Task task=new Task();
Thread thread=new Thread(task);
thread.setDaemon(true);//设置线程为守护线程,守护线程必须在启动之前设置
thread.start();
for(int i=0;i<10000;i++){
System.out.println("main"+i);
}
}
}
多线程 在一个应用程序中,存在多个线程,不同的线程可以并行的执行任务 优点: 提高程序处理能力,提高效率,提高cpu利用率 杀毒软件 垃圾清理 病毒查杀 .... 缺点: 线程也是需要占用内存资源和CPU资源 多个线程对同一个共享的资源进行访问,,会出现线程安全问题
多线程同步
●
多个线程同时读写同一份共享资源时,可能会引起冲突。所以引入线程“同步”机制, 即各线程间 要有先来后到;
如何解决多个线程访问同一个共享资源时不出现问题? 同步 = 排队+锁 一次只能有一个线程访问共享资源 加锁方式1: 使用synchronized关键字修饰代码块 和 方法 修饰代码块 同步对象要求: 多个线程用到的的必须是同一个对象 可以是java中任何类的对象 作用:用来记录有没有线程进入到同步代码中 synchronized(同步对象/锁){ 同步代码块,一次只允许一个线程进入 } 修饰方法: 1.锁不需要我们提供了,会默认提供锁对象 2.synchronized如果修饰的是非静态方法,锁对象是this 3.synchronized如果修饰的是静态方法,锁对象修饰的是类的Class对象 一个类只有一个Class对象
package day16thread.demo6;
/*
柜台角色 共享数据
*/
public class Counter {
int num=1;//代表商品数量
//负责生产商品的方法 锁对象是this add() 和 sub() 用的是同一把锁
public synchronized void add(){
if(num==0){
System.out.println("生产者生产了一件商品");
num=1;//生产了一件商品
this.notify();//唤醒消费者线程
}else{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//负责消费商品的方法
public synchronized void sub(){
if(num>0){
System.out.println("消费者拿走了一件商品");
num=0;//消费者拿走了商品
this.notify();
}else{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package day16thread.demo6;
public class CustomerThread extends Thread{
Counter counter;
public CustomerThread(Counter counter){
this.counter=counter;
}
@Override
public void run() {
counter.sub();//消费者线程一直消费
}
}
package day16thread.demo6;
/*
生产者线程
*/
public class ProducterThread extends Thread{
Counter counter;
public ProducterThread(Counter counter){
this.counter=counter;
}
@Override
public void run(){
counter.add();//生产者线程一直生产
}
}
package day16thread.demo6;
public class Test {
public static void main(String[] args) {
Counter counter=new Counter();//创建的唯一的柜台对象
ProducterThread p=new ProducterThread(counter);
CustomerThread c=new CustomerThread(counter);
p.start();
c.start();
}
}
枷锁方式2: 使用jdk中提供的ReentrantLock类实现加锁 ReentrantLock只能对某一段代码进行加锁,不能对整个方法进行加锁
package day16thread.ticket3;
import java.util.concurrent.locks.ReentrantLock;
public class TicketTask implements Runnable{
int t=10;
//实现枷锁的对象
ReentrantLock reentrantLock=new ReentrantLock();
@Override
public void run() {
while(true){
try{
reentrantLock.lock();//加锁
if(t>0){
System.out.println(Thread.currentThread().getName()+"买了"+t+"票");
t--;
}else{
break;
}
}finally{//在finally块中保证锁必须释放
reentrantLock.unlock();//释放锁
}
}
}
}
package day16thread.ticket3;
public class Test {
public static void main(String[] args) {
TicketTask ticketTask=new TicketTask();
Thread t1=new Thread(ticketTask,"窗口1");
Thread t2=new Thread(ticketTask,"窗口2");
t1.start();
t2.start();
}
}
synchronized 和 ReentrantLock区别 相同点:都实现了加锁功能 不同点: 1.synchronized 是一个关键字,ReentrantLock是一个类 2.synchronized修饰代码块和方法,ReentrantLock只能修饰代码块 3.synchronized可以隐式的加锁和释放锁,运行过程中如出现了异常可以自动释放 R
创建线程方式3: 实现Callable 接口 重写call()方法 call() 可以有返回值,可以抛出异常,还可以自定义返回结果的类型 eentrantLock需要手动的添加锁和释放锁,建议在finally代码块中释放
实现Callable接口与使用Runnable相比,Callable功能更强大些.
•
相比run()方法,可以有返回值
•
方法可以抛出异常
•
支持泛型的返回值
•
需要借助FutureTask类,获取返回结果
package day16thread.demo7;
import java.util.concurrent.Callable;
public class SumTask<T> implements Callable<T> {
@Override
public T call() throws Exception {
Integer i=0;
for(int j=0;j<10;j++){
i+=j;
}
return (T)i;
}
}
package day16thread.demo7;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Test {
public static void main(String[] args) {
SumTask<Integer> sumTask=new SumTask<>();
FutureTask<Integer> futureTask=new FutureTask(sumTask);
Thread thread=new Thread(futureTask);
thread.start();
try {
Integer integer=futureTask.get();//获取到线程返回结果
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}