一,如何实现多线程
在java中实现多线程有三种方式:
1.继承Thread类并重写run方法。
2.实现Runnable接口。
3.实现Callable接口。
下面是代码案例:
(1)继承Thread类并重写run方法实现多线程
通过继承Thread创建多线程类
package thread;
/**
* 继承方式实现多线程
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i<30 ;i++){
System.out.println("MyThread======="+i);
}
}
}
在测试类中创建主线程--->测试输出结果
package thread;
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//开启多线程,自动调用从写的run方法
myThread.start();
for (int i =0;i<30;i++){
System.out.println("main====="+i);
}
}
}
打印结果无规律,因为cpu会分配时间片,当线程同时运行时,线程之间会去抢夺时间片
几种常用获取线程名的方式
(1)通过父类Thread中的getName()方法可以获取线程名称,但是这个类必须为Thread类的子类
package thread;
/**
* 继承方式实现多线程,同时拿到当前线程名称
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i<30 ;i++){
//getname()为父类的方法,作用是获取到该线程的名称
System.out.println(getName()+"======="+i);
}
}
}
(2)通过Thread中的静态方法currentThread获取当前线程,之后调用getName方法获取线程名,这个方式可以在任意处获取线程名称。
package thread;
/**
* 使用Thread.currentThread().getName()方法获取线程名,可以在任意位置获取线程名
*/
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//开启多线程,自动调用从写的run方法
myThread.start();
for (int i =0;i<30;i++){
//Thread.currentThread().getName()方法,Thread类中的currentThread静态方法获取到当前线程,
// getName()获取线程名,可以在任意处获取线程名
System.out.println(Thread.currentThread().getName()+"====="+i);
}
}
}
几种常用的自定义线程名的方式
(1)通过setName的方式自定义线程名。
线程类
package thread;
/**
* 继承方式实现多线程,同时拿到当前线程名称
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i<30 ;i++){
System.out.println(getName()+"======="+i);
}
}
}
测试类中通过线程类.setName()自定义线程名称
package thread;
/**
* 使用Thread.currentThread().getName()方法获取线程名,可以在任意位置获取线程名
*/
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//通过setName的方式自定义线程名称
myThread.setName("线程A");
//开启多线程,自动调用从写的run方法
myThread.start();
for (int i =0;i<30;i++){
//Thread.currentThread().getName()方法,Thread类中的currentThread静态方法获取到当前线程,
// getName()获取线程名,可以在任意处获取线程名
System.out.println(Thread.currentThread().getName()+"====="+i);
}
}
}
(2)通过构造函数更改线程名
通过自身的构造函数,将传进来的name赋值给父类线程名,从而使用构造函数改变线程名称
package thread02;
public class Tickets extends Thread{
int tickets=100;//票数
//通过自身的构造函数,将传进来的name赋值给父类线程名,从而使用构造函数改变线程名称
public Tickets(String name){
super(name);
}
@Override
public void run() {
while (tickets>0){
tickets--;
System.out.println(getName()+"剩余票数为"+tickets);
}
}
}
创建四个线程同时进行,同时给每个线程不同的名称进行测试。
package thread02;
/**
* 多线程实现卖票
*/
public class test {
public static void main(String[] args) {
Tickets A = new Tickets("窗口A");
Tickets B = new Tickets("窗口B");
Tickets C = new Tickets("窗口C");
Tickets D = new Tickets("窗口D");
A.start();
B.start();
C.start();
D.start();
}
}
(2)实现Runnable接口创建多线程类
通过实现Runable创建多线程任务类
实现了Runnable接口的类就是线程任务类,线程任务类需要使用线程对象才能运行
package thread03;
/**
* 使用接口的方式实现多线程
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
//线程任务代码
int i =100;
while (i>0){
i--;
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~"+i);
}
}
}
创建线程对象--->执行线程任务类对象
线程对象中可以放置两个参数,第一个参数为任务类对象,第二个参数为线程名称(可填可不填,不填就是系统默认值)
能使用实现接口的方式不要使用继承父类的方式,因为继承只支持单继承,扩展性比较差
package thread03;
public class test03 {
public static void main(String[] args) {
//线程任务类对象
MyRunnable myRunnable = new MyRunnable();
//创建线程对象,Runnable target
Thread thread = new Thread(myRunnable,"线程A");
/*运行线程对象*/
thread.start();
for (int i =0;i<100;i++){
System.out.println("main=========="+i);
}
}
}
下面的例子为多个窗口共卖一张票,这种情况会出现超卖的现象和卖同一张票的情况。---这就是多线程操作公共数据时出现的线程安全问题=====后面的时候再解决。
package thread04;
public class SellingTickets implements Runnable{
int tickets = 100;
@Override
public void run() {
while (true){
if(tickets>0){
tickets--;
System.out.println(Thread.currentThread().getName()+"买了一张票,还剩票数"+tickets);
}else{
break;
}
}
}
}
package thread04;
public class test {
public static void main(String[] args) {
SellingTickets sellingTickets = new SellingTickets();
Thread thread = new Thread(sellingTickets,"窗口1111");
Thread thread2 = new Thread(sellingTickets,"窗口2222");
Thread thread3 = new Thread(sellingTickets,"窗口3333");
Thread thread4 = new Thread(sellingTickets,"窗口4444");
thread.start();
thread2.start();
thread3.start();
thread4.start();
}
}
二,Thread类中常用的方法
(1)sleep线程休眠的用法及解释
package thread;
/**
* 继承方式实现多线程,同时拿到当前线程名称
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i<30 ;i++){
try {
//使当前线程休眠xxx毫秒之后再执行
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"======="+i);
}
}
}
(2)yiele线程放弃时间片的方法及解释
yiele线程放弃时间片,等待下一次竞争,此方法产生线程交替执行的概率比较高,但并不是百分百
package thread;
/**
* 继承方式实现多线程,同时拿到当前线程名称
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i =0 ; i<30 ;i++){
//使当前线程主动放弃时间片等待下一次竞争
Thread.yield();
System.out.println(getName()+"======="+i);
}
}
}
package thread;
/**
* 使用Thread.currentThread().getName()方法获取线程名,可以在任意位置获取线程名
*/
public class Test01 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("线程A");
myThread.start();
MyThread myThread2 = new MyThread();
myThread2.setName("线程B");
myThread2.start();
}
}
(3)join线程加入的方法用法及解释
join方法,允许其他线程加载到当前线程中,直到其他线程执行完毕之后,当前主线程才会执行,如果有多个线程加入当前线程,那么加入的线程会进行抢夺时间片。
package thread;
/**
* 使用Thread.currentThread().getName()方法获取线程名,可以在任意位置获取线程名
*/
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.setName("线程A");
myThread.start();
MyThread myThread2 = new MyThread();
myThread2.setName("线程B");
myThread2.start();
myThread.join();
myThread2.join();
for (int i =0;i<30;i++){
//Thread.currentThread().getName()方法,Thread类中的currentThread静态方法获取到当前线程,
// getName()获取线程名,可以在任意处获取线程名
System.out.println(Thread.currentThread().getName()+"====="+i);
}
}
}
通过打印结果可以看出主线程最后执行,而加入的线程会进行抢夺时间片执行
(4)**.setPriority()**线程优先级的说明及解释
线程优先级分为1-10十个等级,等级越高说明获取时间片的概率越高,但是这并不是一定的。
(5).setDaemon(true) 守护线程的说明及解释
线程对象.setDaemon(true);设置为守护线程。
线程有两类:用户线程(前台线程)和守护线程(后台线程)
如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。
垃圾回收线程属于守护线程。
三,线程安全的解决方法
这里的临界资源对象指的是用哪个对象当锁,调用同一个临界资源对象的方法,他们便会同步执行,排队执行,而不会发生超卖等现象。 原子操作指的是要么都成功,要么都失败
注意:每个对象都有一个互斥锁标记,用来分配给线程的。 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块。 线程退出同步代码块时,会释放相应的互斥锁标记。也就是开锁
使用同步代码块
格式为:
synchronized(临界资源对象){//对临界资源解锁
//代码(原子操作)
}
示例:
package thread04;
public class SellingTickets implements Runnable{
int tickets = 100;
@Override
public void run() {
while (true){
synchronized (this){//对临界资源对象加锁,也就是this就是锁,也可以为其他对象,这里的临界资源对象只能是对象,当多个方法执行时,他们如果是同一个锁,才会互相排队执行
if(tickets>0){
tickets--;
System.out.println(Thread.currentThread().getName()+"买了一张票,还剩票数"+tickets);
}else{
break;
}
}
}
}
}
package thread04;
public class test {
public static void main(String[] args) {
SellingTickets sellingTickets = new SellingTickets();
Thread thread = new Thread(sellingTickets,"窗口1111");
Thread thread2 = new Thread(sellingTickets,"窗口2222");
thread.start();
thread2.start();
}
}
输出结果:
可以看到现在的票数不会继续发生超卖,重复卖等现象。
注意!!!锁不要直接加在方法上,除非是特殊情况,因为如果直接加在方法上就会导致锁只能被一个线程使用,线程抢到之后执行完毕才会开锁,具体案例如下
package thread04;
public class SellingTickets implements Runnable{
private int tickets = 100;
@Override
public synchronized void run() {
while (true){
if (tickets > 0) {
tickets--;
System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩票数" + tickets);
} else {
break;
}
}
}
}
package thread04;
public class test {
public static void main(String[] args) {
SellingTickets sellingTickets = new SellingTickets();
Thread thread = new Thread(sellingTickets,"窗口1111");
Thread thread2 = new Thread(sellingTickets,"窗口2222");
Thread thread3 = new Thread(sellingTickets,"窗口3333");
Thread thread4 = new Thread(sellingTickets,"窗口4444");
thread.start();
thread2.start();
thread3.start();
thread4.start();
}
}