继承Thread类,重写run()方法
class MyThread extends Thread{
@Override
public void run() {
//要执行的操作
}
}
//调用线程 Thread子类对象调用start()方法
public class ThreadTest{
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();//1.启动线程 2.调用线程的run()方法
}
}
//匿名创建线程
new Thread(){
@Override
public void run() {
super.run();
}
}.start();
不能直接调用run()启动线程,这相当于直接调用类的方法,并不启动线程。
需要开辟几个线程,就new多少个Thread子类对象,调用其父类Thread的start()方法。
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
涉及的方法:
public class ThreadTest2 implements Runnable{
@Override
public void run() {
//要执行的操作
}
}
class MThread {
public static void main(String[] args) {
Thread thread=new Thread(new ThreadTest2());
thread.start();//1.启动线程 2.调用runnable的run方法
}
}
开发中优先选择实现runnable接口的方式
联系:Thread本身也实现了runnable接口
相同点:都需要重写run()方法,将线程要执行的逻辑声明在方法中
#####Thread.State类定义了线程的几种状态
例子
/**
* 创建三个窗口卖票,总票数100,使用Thread继承方式
*问题:买票过程中 出现重票 漏票 负票
*问题出现原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也来操作车票(共享数据被并行操作)
*如何解决:当一个线程在操作ticket时候,其他线程不能参与进来,直到线程a操作完ticket时,其他县城才可以开始操作
*即使线程a阻塞也不能被改变
* @author from z
* @create 2021-01-31 21:11
* 在java中我们通过同步机制来解决线程安全问题
* 方式一:同步代码块
* synchronized(同步监视器){
* //需要同步的代码
* }
* 说明:操作共享数据的代码即为需要被同步的代码 注意(不能包含多了也不能包含少了)
* 共享数据:多个线程共同操作的变量。比如:此代码中ticket
* 同步监视器:俗称锁。任何一个类的对象都可以充当锁
* 方式二:
*/
public class WindowTest {
public static void main(String[] args) {
Window window=new Window();
Window window1=new Window();
Window window2=new Window();
window.setName("窗口1");
window2.setName("窗口3");
window1.setName("窗口2");
window.start();
window2.start();
window1.start();
}
}
class Window extends Thread{
private static int ticket=100;//此时依然存在线程安全问题
@Override
public void run() {
while (true){
if (ticket>0){
System.out.println(getName()+":卖票,票号为"+ticket);
ticket--;
}else {
break;
}
}
}
}
方式一同步代码块
class Window extends Thread{
private static int ticket=100;
static Object o=new Object();//此时的o充当锁,因为是继承thread类的线程,每次创建线程都会重新new对象,所以锁的对象必须是唯一,必须用static修饰,否则锁不相同
@Override
public void run() {
/*synchronized (o) {//同步代码块,此时放在while之外,
则会一个线程执行完所有。锁也不能是this,因为用的是继承thread,
如果用this,则一个对象一个锁,锁不唯一,也会造成不同步*/
while (true) {
//synchronized(Window.class){}类也可以充当监视器,类也是一个对象
synchronized (o){
if (ticket > 0) {
System.out.println(getName() + ":卖票,票号为" + ticket);
ticket--;
} else {
break;
}
}
}
}
方式二:同步方法
//如果操作共享数据的代码完整的声明在一个方法中,我们可以使用同步方法
public class WindowTest1 implements Runnable {
private int ticket=100;
@Override
public void run() {
while (ticket>0){
sTicket();
}
}
// public static synchronized void sTicket(){
//继承thread类的监视器为:this,此时就会出错,因为每个线程对象都有一个新的this,所以锁不唯一,此时只能把方法写成静态static,方法为static之后 锁的对象为class唯一
public synchronized void sTicket(){//同步方法//同步监视器:this
if (ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为"+ticket);
ticket--;
}
}
}
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
在继承Thread继承类创建线程的方式中,可以考虑当前类充当监视器
当使用同步方法时,synchronized的锁是this对象,通过继承创建的线程需要将同步方法声明为静态static,否则锁不唯一,此时锁为 class对象,锁是同一个。
关于同步方法的总结:
同步方法仍然涉及到监视器,只是不需要我们显示的声明
静态同步方法的监视器是当前类本身,非静态同步方法的监视器是当前对象本身
单例模式-懒汉式线程安全问题
//同步方法
class Bank{
private static Bank instance=null;
private Bank(){}
//懒汉式单例模式
public static synchronized Bank getInstance() {
if (instance==null){
instance=new Bank();
}
return instance;
}
}
class Bank{
private static Bank instance=null;
private Bank(){}
//懒汉式单例模式
public static Bank getInstance() {
/** //方式一:效率稍差
synchronized (Bank.class) {
if (instance==null){
instance=new Bank();
}
}*/
//方式二 此时如果第一个线程拿到了单例对象,后面未进入的线程通过第一个if就可以直接取的instance对象,不需要再在锁中等待
//效率快
if (instance==null) {
synchronized (Bank.class) {
if (instance==null){
instance=new Bank();
}
}
}
return instance;
}
}
/**
* 演示死锁的问题
* @author from z
* @create 2021-02-01 22:23
*/
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1=new StringBuffer();
StringBuffer s2=new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable(){
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
说明:不同线程分别占用对方需要的同步资源不放弃,都在等在对方放弃自己需要的同步资源,就形成了线程死锁
1.出现死锁后,不会出现异常,不会出现提示,只是所有线程都处于阻塞状态,无法继续
2.我们使用同步时,要避免出现死锁
/**
* 解决线程安全的方式三:lock锁,jdk5.0新增
* @author from z
* @create 2021-02-01 22:40
*/
public class LockTest {
public static void main(String[] args) {
Window window=new Window();
Thread thread1=new Thread(window);
Thread thread2=new Thread(window);
Thread thread3=new Thread(window);
thread1.start();
thread2.start();
thread3.start();
}
}
class Window implements Runnable{
private int ticket=100;
private ReentrantLock lock=new ReentrantLock(true);//无参为flase 有参是代表公平锁
@Override
public void run() {
while (true){
try {
lock.lock();//调用锁定方法
if (ticket>0){
try {
Thread.sleep( 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":票号"+ticket);
ticket--;
}
else {
break;
}
} finally {
lock.unlock();//调用解锁方法
}
}
}
}
lock锁是ReentrantLock类 需要同步的代码块之前 调用ReentrantLock类的lock()方法 需要try catch 最后一定要finally 因为需要解锁 否则会死锁,解锁调用 unlock()方法
synchronized和lock的异同:二者都可以解决线程安全
synchronized机制在执行完相应的同步代码之后自动的释放同步监视器
lock需要手动的去启动同步同时需要结束同步
/**
* 线程通信例子:使用两个线程打印1-100,线程1,线程交替打印
* 涉及到的三个方法
* wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
* notify():唤醒被wait的一个线程,如果多个线程被wait 则唤醒优先级最高的
* notifyAll():唤醒所有wait的线程
*
* 说明:1.三个方法必须使用在同步代码块或同步方法
* 2.三个方法的调用者 必须是同步代码块或同步方法中的同步监视器,否者会出现异常
* 3.三个方法定义在Object类中
* @author from z
* @create 2021-02-02 13:35
*/
public class CommunicationTest {
public static void main(String[] args) {
Number number=new Number();
Thread thread1=new Thread(number);
Thread thread2=new Thread(number);
thread1.setName("线程1");
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
class Number implements Runnable{
private int number=1;
@Override
public void run() {
while (true){
synchronized (this) {
notify();//唤醒线程,当线程1阻塞,线程2拿到锁进来唤醒线程1
//notifyAll();唤醒多个线程 按优先级
if (number < 100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait();//使得调用如下方法的内容进入阻塞状态此时会释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
sleep()和wait()
一旦执行,都可以使当前线程进入阻塞状态
sleep在Thread类中声明,wait在Object类中声明
sleep可以在任何需要场景下调用
wait必须在同步代码块或同步方法中
wait会释放同步监视器
方式一:实现Callable接口
/**创建线程方式三:Callable接口
*
*如何理解实现callable接口比实现runnable接口更强大:
* call()可以有返回值,可以抛异常,callable支持泛型
* @author from z
* @create 2021-02-02 14:24
*/
//1.创建一个实现Callable接口的实现类
class NumThread implements Callable{
//2.实现call()方法,将线程需要执行的操作声明在call()方法中 有返回值
@Override
public Object call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
if (i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable实现类的对象
NumThread numThread=new NumThread();
//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask=new FutureTask(numThread);
//5.将futuretask对象作为参数传递到Thread构造器中创建线程对象开启线程
new Thread(futureTask).start();
try {
//get()方法返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
Object o=futureTask.get();
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
方式二:创建线程池
/**创建线程方式四:使用线程池
*
* 好处:
* 1.提高响应速度(减少创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
* 3.便于线程管理
* corePoolSize:核心池的大小
* maximumPoolSize:最大线程数
* keepAliveTime:线程没有任务是最多保持多长时间后会终止
* @author from z
* @create 2021-02-02 15:48
*/
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//提供指定数量线程的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor executor=(ThreadPoolExecutor) executorService;
//设置线程池属性
executor.setCorePoolSize(15);
//executor.setKeepAliveTime();
//执行指定的线程操作。需要提供实现runnable接口或callable接口实现类的对象
executorService.execute(new NumberThread());//适合使用runnable
//executorService.submit();//适合使用callable
//关闭连接池
executorService.shutdown();
}
}
创建多线程四种方式!