一、线程
线程: 进程内部一个独立执行单元(通向CPU的一条路径.)
1. Thread类:
常用方法:
Thread.currentThread().getName(); 获取当前线程的名称.
Thread.sleep(long millis): 让当前线程睡眠多少毫秒,之后继续执行线程。
使用继承类的方式创建线程:
a.创建一个Thread的子类 , 继承Thread类.
b.重写Thread类的run方法 , 设置线程要执行的任务.
c.创建Thread的子类对象.
d.调用Start方法 , - - - - > 开启新线程 .
// 1. 创建一个子类 , 继承Thread类 .
public class MyThread extends Thread{
// 2.重写run方法
@Override
public void run() {
// 设置线程任务 -- 获取当前正在执行的线程的名称
System.out.println(Thread.currentThread().getName());
}
}
// 测试类
public class Demo01GetThreadName {
public static void main(String[] args) {
System.out.println("这里是main线程.");
// 3. 创建子类对象 MyThread
MyThread mt = new MyThread();
// 4. 调用start方法, 开启线程
mt.start();
// 获取主线程的名称.
String name = MyThread.currentThread().getName();
System.out.println(name);
}
}
2. Runnable接口(重点)
使用Runnable接口的好处:
a.避免了实现类单继承的局限性(还可以继承其他类, 实现其他接口.)
b.降低了程序的耦合性,提高了程序的扩展性.
实现方式:
1.创建一个类实现Runnable接口.
2.重写接口中的run方法 , 设置线程任务.
3.创建接口的实现类对象 .
4.创建Thread类对象 , 在构造方法中传递Runnable接口的实现类对象
5.调用Threa类中start开启新线程.
// 1. 创建一个子类 , 实现 Runnable 接口 .
public class RunnableImpl implements Runnable {
// 2. 重写run方法 , 设置线程任务.
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
// 测试类
public class Demo01Runnable{
public static void main(String[] args) {
// 3.创建接口的实现类对象 .
RunnableImpl r = new RunnableImpl();
// 4.创建Thread类对象 . 接口的实现类对象作为参数
Thread t = new Thread(r);
// 5.开启新线程
t.start();
// 3 , 4 , 5的简写步骤 .
new Thread(new RunnableImpl2()).start();
// 主线程开启完新的线程,会继续执行
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
3. 匿名内部类:
匿名内部类方式开启多线程:
匿名内部类作用: 简化代码
格式:
new 父类/接口( ){
重写父类/接口中的方法
};
// 匿名内部类的写法:
public class Demo01Thread {
public static void main(String[] args) {
// Runnable接口的匿名内部类
new Thread(new Runnable(){
@Override
public void run() { // 重写run方法 , 设置线程任务.
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"----"+i);
}
}
}).start(); // 开启线程.
// 主线程
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
二、线程安全
出现线程安全的问题:
多线程访问了同一个共享的数据。
使用线程安全:
a.增加了程序的安全性.
b.降低了效率.
1. 同步代码块
synchronized(锁对象){
可能出现安全问题的代码
(访问了共享数据的代码)
}
注意:
1.锁对象,可以是任意的对象
2.必须保证多个线程使用的是同一个锁对象
/*
卖票案例出现了线程安全问题:出现了重复的票和不存在的票
解决线程安全问题的第一种方式:使用同步代码块
*/
// 创建子类 , 实现Runnnabl接口
public class RunnableImpl implements Runnable {
// 定义一个共享的票
private int ticket = 100;
// 重写run方法 .
@Override
public void run() {
// 使用同步代码块 . 保证线程的安全 .
synchronized (this){
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!");
ticket--;
}
}
}
}
}
2.同步方法
同步锁是谁?
同步方法中 : 同步锁就是 t h i s(谁调用 , 谁就是this)
静态同步方法中: 当前方法所在类的字节码对象(类名.class)。
// 1 . 创建一个类 , 实现runnable接口
public class RunnableImpl2 implements Runnable {
// 创建一个共享的票
private static int ticket = 100;
// 重写run方法 ,
@Override
public void run() {
// 线程任务 - 卖票
while (true){
getTicket();
}
}
/*
定义一个卖票的方法
1.定义一个方法,添加一个同步synchronized修饰符
同步方法的锁对象使用的是this
哪个对象调用的方法,方法中的this就是哪个对象,本类的对象
this就是Runnable的实现类对象RunnableImpl
*/
public synchronized void getTicket() {
//2.把访问了共享数据的代码,放到同步方法中
// synchronized (this) {
if (ticket > 0) {
//为了提高安全问题出现的几率,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!");
ticket--;
}
//}
}
}
3.Lock锁
接口中的方法:
void lock() 获取锁。
void unlock() 释放锁。
同步锁使用的弊端:
当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
public class RunnableImpl3 implements Runnable {
private int ticket= 100;
// 1. 在成员位置创建一个Lock接口的实现类对象ReentrantLock
Lock r = new ReentrantLock();
@Override
public void run() {
while (true){
r.lock(); // 2.在可能会出现线程安全问题的代码前调用lock方法获取锁对象
if (ticket > 0) {
//为了提高安全问题出现的几率,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票!");
ticket--;
}
r.unlock(); // 3.在可能会出现线程安全问题的代码后调用unlock方法释放锁对象
}
}
}
4.等待(wait)与唤醒(notify)
a. 必须使用锁对象调用 .
b. wait和notify方法一般使用在同步中