线程的基本语法
一、线程与进程
线程:最小的执行单位
进程:计算机分配和调度资源的最小单位
程序的运行:计算机分配的计算机线程在各个进程之间来回的切换,因为时间片段很短,所以感觉一直在运行
进程与进程之间的数据不会共享;线程与线程之间的数据是共享;Java中一个进程中包含多个线程
多线程编程效率不一定比单线程高,因为需要考虑线程切换带来的时间开销
二、创建线程的方式
(一)继承Thread类
1.定义一个类继承Thread
2.重写run方法
3.创建线程类的对象
4.开启线程,调用start方法
(二)实现Runnable接口
1.定义一个类实现Runnable接口
2.重写run方法
3.创建类的对象
4.创建一个线程对象,分配新的 Thread 对象 Thread(Runnable target)
如果参数传递要求是一个接口,首先想到多态
a.去创建一个类实现该接口 b.匿名内部类
5.调用start方法
注意事项:
start方法才会开启一个新的线程,它会自动调用线程中的run方法,而如果直接使用run方法,相当于对象调用普通方法,不会开启新的线程
实现Runnable接口和继承Thread类两者的区别
继承类只能是单继承,而实现接口可以多实现,可以提高代码的扩展性
(三)使用Callable和Future创建线程
1.创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
2.使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
3.使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
4.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
public class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
//call()方法可以有返回值
return null;
}
}
public class Demo {
public static void main(String[] args) {
//创建Callable实现类的对象
MyThread myThread = new MyThread();
//并使用FutureTask来包装Callable实现类对象
FutureTask<Integer> task=new FutureTask<Integer>(myThread);
//使用Thread包装FutureTask对象
Thread t1=new Thread(task,"有返回值的线程");
t1.start();
for(int i=0;i<1000;i++){
System.out.println(Thread.currentThread().getName()+"===="+i);
}
try {
System.out.println("子线程的返回值:"+task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、常用方法
1.设置线程的名称
void setName(String name);
2.获取线程的名称
String getName()
3.返回当前线程对象
static Thread currentThread();
四、线程安全问题(掌握)
多个线程同时去访问同一个数据的时候,有可能产生线程安全问题
Java中保证线程安全的方式
继承需加上static修饰,实现不需要
(一)同步 synchronize
1.同步方法
监听对象:
(1)监听String类型的对象,因为String有常量池
(2)监听其他类型的对象,需加上static修饰
(3)监听类的字节码文件对象 类名.class
this不行,因为指代的是当前线程对象
private static int num = 50;
String str = "票";
static Integer i = new Integer(1);
@Override
public void run() {
while(num>0) {
//synchronized (SafeThread.class) {
//synchronized (str) {
synchronized (i) {
if(num>0){
System.out.println(Thread.currentThread().getName()+"你的票号是:"+num);
num--;
}
}
}
}
2.同步代码块
@Override
public void run() {
while(num>0) {
safe();
}
}
synchronized static void safe(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"你的票号是:"+num);
num--;
}
}
(二)加锁 ReentrantLock
(1)对核心代码进行加锁 lock
最好放在try-catch结构中
(2)最后释放锁 unlock
最好放在finally结构中
加锁与同步区别
加锁相对于同步而言 灵活性更高效率较高,且同步由jvm来维护,加锁是代码级别的,需要手动释放
static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(num>0) {
lock.lock();
try {
if(num>0){
System.out.println(Thread.currentThread().getName()+"你的票号是:"+num);
num--;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
五、线程休眠sleep()
线程类Thread当中有一个static void sleep(long millis)方法,在指定的毫秒数内让当前正在执行的线程休眠
六、线程的状态
创建 ----> 就绪 ----> 执行 ----> 死亡(在执行时可能会产生阻塞,恢复时又回到就绪状态)
(1) 创建:线程刚被创建
(2) 就绪:线程已经被启动,正在等待被分配给CPU时间片
(3) 执行:获得CPU分配的时间片,正在执行任务
(4) 阻塞:由于某种原因导致正在运行的线程让出CPU并暂停自己的执行(如CPU分配的时间片用完)
(5) 死亡:线程中的所有代码、任务完成或被其他线程杀死