线程与进程
进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间(不共享的堆栈)。
线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
线程调度
- 分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
- 抢占式调度: 优先让优先级高的线程使用 CPU(优先级高抢到时间片的概率越高),如果线程的优先级相同,那么会随机选择一个(线程随机性)。Java使用的是抢占式调度。
同步与异步
同步: 排队执行 , 效率低但是安全。
异步: 同时执行 , 效率高但是数据不安全。
创建多线程
方法1: 继承Thread
- 编写类继承Thread类,重写run(),将分支线程要执行的任务代码声明在run()中
- 创建Thread类的子类对象
- 通过此对象调用start(),启动新线程
public class Demo {
public static void main(String[] args){//main方法是运行在主线程里
MyThread m = new MyThread();
m.start();//开启新任务
for(int i=0; i<10; i++)
System.out.println("a" + i);
}
}
public class MyThread extends Thread{//分支线程
public void run(){
for(int i=0; i<10; i++)
System.out.println("b" + i);
}
}
通过匿名内部类继承Thread实现多线程
public static void main(String[] args){
new Thread(){ //匿名内部类
public void run(){
for(int i=0; i<10; i++)
System.out.println("b" + i);
}
}.start();
for(int i=0; i<10; i++)
System.out.println("a" + i);
}
方法2: 实现Runnable
- 编写类实现Runnable接口,重写run()
- 创建任务对象
- 创建线程,并传入任务对象
- 执行这个线程
public class Main {
public static void main(String[] args){
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
//new Thread(new MyRunnable()).start();
...
}
}
public class MyRunnable implements Runnable{//给线程执行的任务
public void run(){
...
}
}
方法3: 带返回值的线程Callable
- 编写类实现Callable接口 , 实现call方法
- 创建Callable类对象
- 创建FutureTask对象,并传入Callable类对象
- 通过Thread,启动线程
public class Demo{
public static void main(String[] args){
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);//创建任务对象
new Thread(task).start();
Integer j = task.get();
...
}
static class MyCallable implements Callable<Integer>{
public Integer call() throws Exception{
...
return 100;
}
}
}
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行(先执行子线程的内容再接着执行主线程的内容),如果不调用不会阻塞。
FutureTask 类的普通方法
Object get():等待计算完成,然后检索其结果。
boolean isDone():判断子线程是否执行完毕,返回true如果任务已完成。
boolean cancel(boolean mayInterruptIfRunning):尝试取消执行此任务。
参数: mayInterruptIfRunning - true如果执行该任务的线程应该被中断; 否则,正在进行的任务被允许完成
返回: false如果任务无法取消,通常是因为它已经正常完成; true任务成功取消
Thread类中的常用方法
构造方法
Thread():分配一个新的 Thread对象。
Thread(Runnable target)
Thread(Runnable target, String name):给线程起名字
普通方法
boolean isAlive():测试这个线程是否活着(执行完run方法之后线程就死亡了)
void start():启动当前线程(即调用start方法的线程),并调用当前线程的run方法
long getId():返回此线程的标识符
String getName():返回此线程的名称。
int getPriority():返回此线程的优先级。
void run():将创建的线程需要执行的操作写在run方法中
void setDaemon(boolean on):将此线程标记为daemon线程或用户线程。
void setName(String name):将此线程的名称更改为等于参数 name 。
void setPriority(int newPriority):更改此线程的优先级。
static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),用Thread.sleep(1000)调用
static Thread currentThread():返回当前线程对象,静态方法用Thread.currentThread()调用
eg. Thread.currentThread().getName()//返回当前线程对象的名称
void interrupt():中断这个线程。
线程不安全问题
java允许多线程并发控制,当多线程同时操作一个可共享的资源变量时,将会导致数据不准确,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程调用,从而保证了该变量的唯一性和准确性
隐式锁 synchronized
解决方案1:同步代码块
格式:synchronized(锁对象){}
java中任何对象都可以作为锁对象/打上锁标记
public class Main {
public static void main(String[] args) {
Runnable run = new Ticket();//创建线程任务
//两个线程执行同一个任务对象run,使用同一个锁对象o
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable {
private Object o = new Object();//锁对象
public void run() {
while (true) {
//把o放在这里,两个线程就有2个锁对象,两个线程就不会排队
//private Object o = new Object();
synchronized (o) {
//被锁住/排队的内容是if语句
...
}
}
}
}
}
解决方案2:同步方法
同步非静态方法
对方法加synchronized修饰符,相等于整个方法都用this实例加锁:synchronized(this){},实现排队执行此方法。
java的成员同步方法,使用的锁是调用该方法的当前对象,即this。
下面两种写法是等价的:
public class Main {
public static void main(String[] args){
Runnable run = new Ticket();//创建任务对象
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
private int count = 10;
public void run(){
while(true){
sale();
}
}
(写法1)public void sale()() {
synchronized(this) {//this是调用此方法的线程对象
...
}
(写法2)public synchronized void sale() { // 当前线程对象调用此方法时,其他线程对象不可调用
...
}
}
}
同步静态方法
对一个静态方法添加synchronized修饰符,相当于锁住的是该类的Class实例:synchronized(类名.class){}
java的静态同步方法,使用的锁是该方法所在类的类对象,即类名.class。
public synchronized static void sale() {
...
}
public class Ticket implements Runnable{
public static void sale () {
synchronized(Ticket.class){
...
}
}
}
显式锁 Lock
解决方案3: 创建锁对象,手动上锁解锁
Lock的子类是ReentrantLock
在任务类中创建ReentrantLock对象属性
public class Main {
public static void main(String[] args){
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
}
public class Ticket implements Runnable{
//创建显示锁
private Lock l = new ReentrantLock();
public void run(){
while(true){
l.lock(); //上锁
...
l.unlock();//解锁
}
}
}
等待唤醒机制
wait(): 会让线程处于等待状态,其实就是将线程临时存储到了线程池中。当前线程必须拥有此对象的监视器(锁),否则抛出java.lang.IllegalMonitorStateException
notify(): 会唤醒线程池中任意一个等待的线程。
notifyAll(): 会唤醒线程池中所有的等待的线程。
这些方法属于Object类,必须使用在同步中,因为必须要标识wait、notify等方法所属的锁。同一个锁上的notify,只能唤醒该锁上wait的线程。默认是this.wait();this.notify();this.notifyAll()。