线程
Thread类
Java.lang.Thread类定义了有关线程的一些方法:
构造方法:
public Thread() :分配一个新的线程对象。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
常用方法:
public String getName() :获取当前线程名称。
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run() :此线程要执行的任务在此处定义代码。
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数
**ps:**创建线程的方式有两种,一种是继承Thread类方式,一种是实现Runnable接口方式
创建线程方式二(较为常用)
创建多线程程序的第二种方式:实现Runnable接口
java.lang.Runnable
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为 run 的无参数方法。
.lang.Thread类的构造方法
Thread(Runnable target) 分配新的 Thread 对象。
Thread(Runnable target, String name) 分配新的 Thread 对象。
实现步骤:
1、创建一个Runnable接口的实现类
2、在实现类中重写Runnable接口中的run方法,设置线程任务
3、创建一个runnable接口的实现类对象
4、创建Tread对象,构造方法中传递Runnablejie接口的实现类对象
5、调用Thread类中start方法,开启新的线程执行run方法
tips:Runnable对象仅仅作为Thread对象的Target,Runnable实现类里包含的run方法仅作为线程的知兴替。而实际线程对象依然是Tread实例,只是该Tread线程负责执行其Target的run()方法
package RunnableTest;
//1、创建一个Runnable实现类
public class RunnableImpl implements Runnable{
//2、在实现类中重写Runanble接口run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
package RunnableTest;
public class RunnableTestDemo {
public static void main(String[] args) {
//3、创建一个runnable接口的实现类对象
RunnableImpl run =new RunnableImpl();
//4、创建Thread类对象,构造方法中传递runnable接口实现类对象
Thread t = new Thread(run);
//5、调用Thread类中start方法,开启新的线程执行run方法
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 class Demo01InnerClassThread {
public static void main(String[] args) {
//线程的父类是Thread
// new MyThread().start();
new Thread(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"黑马");
}
}
}.start();
//线程的接口Runnable
//Runnable r = new RunnableImpl();//多态
Runnable r = new Runnable(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"程序员");
}
}
};
new Thread(r).start();
//简化接口的方式
new Thread(new Runnable(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"传智播客");
}
}
}).start();
}
线程安全
线程安全
package RunnableTest;
public class Ticket implements Runnable {
//定义一个多个线程共享的票据
private int ticket =100;
//设置线程任务:卖票
@Override
public void run() {
while(true){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket+"张票");
ticket--;
}
}
}
}
package RunnableTest;
public class Demo01Ticket {
public static void main(String[] args) {
Ticket tic= new Ticket();
Thread th1 =new Thread(tic);
Thread th2 =new Thread(tic);
Thread th3 =new Thread(tic);
th1.start();
th2.start();
th3.start();
}
}
运行上述代码块出现了两个问题:
1、相同的票数,比如2这张票卖了两回
2、不存在的票,比如0或-1票是不存在的
出现上述问题为线程不安全
ps:线安全问题都是由全局变量及静态变量引起的,若每个线程中对全局变量,静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的,若有多个线程同时执行写操作,一般都要考虑线程同步,否则的话就可能影响线程安全。
线程同步
当我们使用多个线程访问同一资源的时候,且多个线程对程序都有写的操作,就容易出现线程安全问题
要解决上述多线程并发访问一个资源的安全性问题:也就是解决票不存在或重复问题,Java提供了同步机制(synchronized)来解决
为了保证每个线程都能正常执行原操作,Java引入了同步机制,有如下三种方式完成同步操作:
1、同步代码块
2、同步方法
3、锁机制
同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
eg:Object obj =new Object();//创建一个锁对象
package RunnableTest;
public class TicketRunnableIplm01 implements Runnable{
//定义一个多个线程共享的票据
private int ticket =100;
//创建一个锁对象
Object obj = new Object();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while (true){
//同步代码块
synchronized (obj){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket+"张票");
ticket--;
}
}
}
}
}
package RunnableTest;
public class TicketRunnableIplm01Demo {
public static void main(String[] args) {
TicketRunnableIplm01 ticket = new TicketRunnableIplm01();
//TicketRunnableImpl02 ticket = new TicketRunnableImpl02();
// ticket = new TicketRunnableImpl03();
Thread t1= new Thread(ticket);
Thread t2= new Thread(ticket);
Thread t3= new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}
注意:
1、锁对象,可以是任意对象
2、多个线程对象 要使用同一把锁
3、锁对象作用:
把同步代码块锁着,只让一个线程在同步代码块中执行,其他线程只能在外等着(block)
package RunnableTest;
/*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁?
就是实现类对象 new RunnableImpl()
也是就是this
*/
// public synchronized void payTicket(){
public class TicketRunnableIplm04 implements Runnable{
private int ticket =100;
@Override
public void run() {
System.out.println("this:"+this);
//使用死循环,让卖票操作重复执行
while (true){
//同步代码块
synchronized (this){
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket+"张票");
ticket--;
}
}
}
}
}
package RunnableTest;
public class TicketRunnableIplm01Demo {
public static void main(String[] args) {
//TicketRunnableIplm01 ticket = new TicketRunnableIplm01();
//TicketRunnableImpl02 ticket = new TicketRunnableImpl02();
//TicketRunnableImpl03 ticket = new TicketRunnableImpl03();
TicketRunnableIplm04 ticket = new TicketRunnableIplm04();
System.out.println("run:"+ticket);
Thread t1= new Thread(ticket);
Thread t2= new Thread(ticket);
Thread t3= new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}
/*
静态的同步方法
锁对象是谁?
不能是this
this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
*/
// public static synchronized void payTicketStatic(){
synchronized (RunnableImpl.class){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
// }
使用同步方法
使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在外等着
格式:
public synchronized void method(参数列表){
可能会产生线程安全问题的代码
}
使用步骤:
1、把访问了共享数据的代码抽取出来,放到一个方法当中
2、在这个方法添加synchronized修饰符
package RunnableTest;
public class TicketRunnableImpl02 implements Runnable{
//定义一个多个线程共享的票据
private int ticket =100;
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while (true){
sellTicket();
}
}
public synchronized void sellTicket() {
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket+"张票");
ticket--;
}
}
}
使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock接口中的方法:
void lock()获取锁。
void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock implements Lock接口
使用步骤:
1、在成员位置创建一个ReentrantLock对象
2、在可能出现安全问题的代码前调用Lock接口中的方法lock获取锁
Lock lock= new ReentrantLock();
3、在可能出现安全问题代买前调用Lock接口中的unLock释放锁
package RunnableTest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketRunnableImpl03 implements Runnable {
//定义一个多个线程共享的票据
private int ticket =100;
Lock lock= new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while (true){
lock.lock();
if (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticket+"张票");
ticket--;
}
lock.unlock();
}
}
}
package RunnableTest;
public class TicketRunnableIplm01Demo {
public static void main(String[] args) {
//TicketRunnableIplm01 ticket = new TicketRunnableIplm01();
//TicketRunnableImpl02 ticket = new TicketRunnableImpl02();
TicketRunnableImpl03 ticket = new TicketRunnableImpl03();
Thread t1= new Thread(ticket);
Thread t2= new Thread(ticket);
Thread t3= new Thread(ticket);
t1.start();
t2.start();
t3.start();
}
}