文章目录
多线程的创建
Thread类创建
实现步骤:
1. 创建Thread的子类
2. 重写Thread类中的run方法,设置线程任务
3. 创建Thread的子类对象
4. 调用Thread方法中的start方法,开启新线程调用run方法void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法。
注意:多次启动一个线程是非法的,特别是当前线程结束后,不能再重新启动
// 1. 创建一个Thread类的子类
public class MyThread extends Thread{
// 2. 继承Thread类重写run方法
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("run:" + i);
}
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:" + i);
}
}
执行以上代码可以看到一个进程一个main进程共同执行,实现了多线程
线程名获取
方法一:
1. 使用Thread中的方法getName()
String getName() 返回此线程的名称。
public class MyThread01 extends Thread {
@Override
public void run() {
String name = getName();
System.out.println(name);
}
}
public static void main(String[] args) {
MyThread01 mt = new MyThread01();
mt.start(); //Thread-0
MyThread01 mt2 = new MyThread01();
mt2.start(); //Thread-1
方法二:
可以获取当前正在执行的线程,
使用线程中的方法getName()获取线程名称
static Thread currentThread()
返回当前正在执行的线程对象的引用
public class MyThread01 extends Thread {
@Override
public void run() {
Thread thread = Thread.currentThread();
System.out.println(thread);
}
}
public static void main(String[] args) {
MyThread01 mt = new MyThread01();
mt.start(); // Thread[Thread-0,5,main]
MyThread01 mt2 = new MyThread01();
mt2.start(); //Thread[Thread-1,5,main]
}
获取主线程名称需要先获取当前正在执行的 线程然后再getName,则可以用以下方法:
public class MyThread01 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Demo02Thread {
public static void main(String[] args) {
MyThread01 mt = new MyThread01();
mt.start(); //Thread-0
MyThread01 mt2 = new MyThread01();
mt2.start(); //Thread-1
System.out.println(Thread.currentThread().getName()); //main
}
}
设置线程名称
方法一:
使用Thread中的setName(名字)
方法二:
创建带参构造方法,参数传递线程名称:
使用父类的带参构造方法,把线程名称传递给父类,让父类给子线程起名字
Thread(String name) 分配新的Thread对象
public class MyThread extends Thread{
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.setName("dada");
mt.start();
MyThread mt2 = new MyThread("aaa");
mt2.start();
}
sleep
public static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停
public static void main(String[] args) {
// 模拟秒表
for (int i = 1; i <= 60; i++) {
System.out.println(i);
// 使用Thread类的Sleep方法让程序睡眠1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
实现Runnable接口创建
java.long.Runnable
Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run 。
java.long.Thread类的构造方法
Thread(Runnable target) 分配一个新的 Thread对象。
实现步骤:
1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable实现类对象
5.调用Thread类中的start方法
public class RunnableImpl implements Runnable{
@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) {
RunnableImpl rn = new RunnableImpl();
Thread t = new Thread(rn);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
实现Runnable创建多线程的好处:
1.避免了单继承的局限性
一个类只能继承一个类,类继承了Thread类就不能继承其他类
实现了Runnable接口,还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程的任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:开启新线程
匿名内部类创建
格式:
new 父类/接口(){
重复父类/接口中的方法
}
public static void main(String[] args) {
// 线程的父类使Thread
new Thread(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}.start();
//线程的接口
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + ":---" + i);
}
}
};
new Thread(r).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+":+++"+i);
}
}
}).start();
}
多线程安全问题
假设有一家电影院买票,如果只有一个窗口买票,不会出现卖出去的票有重复的座位或者不存在的座位,就相当于之前的单线程程序,而现在使用了多线程如果不对其同步代码进行约束则会出现上述问题,例如:
public class RunnableImpl implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
public static void main(String[] args) {
RunnableImpl run = new RunnableImpl();
Thread t0 = new Thread(run);
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t0.start();
t1.start();
t2.start();
}
如图所示会出现重复票,不存在的票情况,这是因为当运行到RunnableImpl的if语句后 t0 进入sleep,此时 t1,t2 也可能会进入里面,其中一个先sleep结束进行了减一操作,而其他已经经过了 if 判断,唤醒时不需要判断就可以继续向下,这样就产生了不存在的票 如果三个线程同时唤醒就出现了重复的票
同步代码块解决安全感问题
解决线程安全问题一:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:把同步代码块所著,只让一个线程在同步代码块中执行
public class RunnableImpl implements Runnable{
private int ticket = 100;
// 创建锁对象
Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
主函数与之前相同。
原理:
线程抢夺cpu执行权,当一个程序抢到执行权进入同步代码块,这时会首先看synchronized代码中是否有锁对象,如果有就可以进入到锁对象执行
在一个线程进入到锁对象执行时,如果其他对象进入到同步代码块,这是synchronized代码中没有锁对象,会进入堵塞状态,直到这个线程释放
使用同步方法解决安全问题
解决线程安全问题而:使用同步方法:
步骤:
1. 把访问了共享数据的代码抽取出来,放到一个方法中
2. 在方法上添加synchronized修饰符
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
可能会出现共享数据的代码
}
同步方法:
public class RunnableImpl implements Runnable{
private static int ticket = 100;
// 创建锁对象
Object obj = new Object();
@Override
public void run() {
while (true){
payTicket();
}
}
/*
定义一个同步方法
*/
public synchronized void payTicket(){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
public /*synchronized*/ void payTicket(){
synchronized (this){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
同步静态方法:
public class RunnableImpl implements Runnable{
private static int ticket = 100;
// 创建锁对象
Object obj = new Object();
@Override
public void run() {
while (true){
payTicket();
}
}
/*
定义一个静态方法
锁对象不是this
锁对象是本类的class属性-->class文件对象
*/
public static/* synchronized*/ void payTicket(){
synchronized (RunnableImpl.class){
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 买票
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
Lock解决安全问题
解决线程安全问题三:Lock锁
java.util.concurrent.locks.lock接口
lock实现提供了比synchronized方法和语句可获得的更广泛的锁定操作
实现方法:
void lock() 获得锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock impl Lock接口
使用步骤:
1. 在成员位置创建一个ReentrantLock对象
2. 在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
3. 在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
public class RunnableImpl implements Runnable{
private int ticket = 100;
// 1. 在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
// 2. 在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock();
if (ticket>0){
try {
Thread.sleep(30);
// 买票
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// 3. 在可能出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock(); // 无论程序是否异常都会把锁释放,提高程序效率
}
}