Java线程--扩展
扩展一些Java线程的知识。
首先说一下线程相关的常用类:
1. Timer:定时器、调度器、计时器。
2. TimerTask:定时任务。
下面根据这两个类写一个欢迎界面:
import java.awt.BorderLayout; importjava.awt.CardLayout; import java.util.Timer; import java.util.TimerTask;
import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel;
import com.sun.awt.AWTUtilities;
publicclassLogoextends JFrame{ privateintniIndex = 1; private JLabel jbShow; private Timer timer; public Logo(){ JPanel pnBasic = new JPanel(); //设置布局为东西南北中布局 pnBasic.setLayout(new BorderLayout()); jbShow = new JLabel(); //添加图片,图片的路径可以自己设定 jbShow.setIcon(new ImageIcon("p_w_picpath\\logo\\1.png")); //把标签添加进来吗,并放在中间的位置 pnBasic.add(jbShow,BorderLayout.CENTER);
setContentPane(pnBasic); //下面两句是设置窗口透明 setUndecorated(true); AWTUtilities.setWindowOpaque(this, false); //设置标题 setTitle("欢迎界面"); //设置窗口大小 setBounds(200,200,500,400); //设置窗口自适应 pack(); //设置推出方式 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //设置窗口可见 setVisible(true); //创建定时器 timer = new Timer(); //安排指定的任务在指定的时间开始进行重复的固定延迟执行 //task - 所要安排的任务 //firstTime - 首次执行任务的时间 //period - 执行各后续任务之间的时间间隔,单位是毫秒 timer.schedule(new LogoMoniter(),1000,500); } privateclass LogoMoniter extends TimerTask{ @Override publicvoid run() { if(niIndex<7){ niIndex++; jbShow.setIcon(new ImageIcon("p_w_picpath\\logo\\"+niIndex+".png")); }else{ //终止此计时器,丢弃所有当前已安排的任务 timer.cancel(); //销毁窗口 dispose(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } //两秒之后再控制台输出hello System.out.println("hello"); } } } publicstaticvoid main(String[] args) { new Logo(); } } |
这是利用定时器做的一个简单的欢迎界面。我们可以自己添加图片,注意把图片的名字命名成数字.的形式,因为我们是根据for循环来获取图片名字的。 |
接下来说一个Java线程中的问题,比如我们去银行存取钱,如果网络存在一点延迟,假设你的亲人就在网络延迟的时候也在存取钱,那么可能最后的余额就会出现问题,下面通过程序来具体看一下:
publicclass Bank1 { publicstaticvoid main(String[] args) { Account1 account = new Account1(); Person you1 = new Person(account); Person youGF1 = new Person(account); Thread th1 = new Thread(you1); Thread th2 = new Thread(youGF1); th1.start(); th2.start();
try { Thread.sleep(4000);// 确保账户操作完毕 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最终余额:" + account.balance); } }
class Account1 { publicintbalance = 2000; //取钱 publicvoid withdraw() { int temp = balance; temp = temp - 800; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } balance = temp; System.out.println(Thread.currentThread().getName() + ":余额:" + this.balance); } }
class Person implements Runnable { private Account1 account; public Person(Account1 account) { super(); this.account = account; } @Override publicvoid run() { account.withdraw(); } } |
这个程序完成的功能是两个人取同一个账户里的钱。 执行结果: Thread-0:余额:1200 Thread-1:余额:1200 最终余额:1200 |
通过结果可以看到,没取之前余额是2000,而两个人每次取的钱都是800,正常情况余额应该是400,这就是我们所说的问题,那么应该如何解决呢?
线程间的同步:本质:把异步转化为同步,把并行转为串行。
有两种方式可以解决:
1. 同步语句块:synchronized(obj){}
2. 同步方法:在方法前加synchronized修饰符,拿到的仍然是本类对象的锁。
难点:不容易测出来。
对象锁:每个对象都有一把锁。同步锁、互斥锁。
下面通过程序看一下:
publicclass Bank1 { publicstaticvoid main(String[] args) { Account1 account = new Account1(); Person you1 = new Person(account); Person youGF1 = new Person(account); Thread th1 = new Thread(you1); Thread th2 = new Thread(youGF1); th1.start(); th2.start();
try { Thread.sleep(4000);// 确保账户操作完毕 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最终余额:" + account.balance); } }
class Account1 { publicintbalance = 2000; private Object obj = new Object();
// 加了一把互斥锁,只能有一个线程执行 public/*synchronized*/void withdraw() { //两种方法 synchronized (obj) { int temp = balance; temp = temp - 800; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } balance = temp; System.out.println(Thread.currentThread().getName() + ":余额:" + this.balance); } } }
class Person implements Runnable { private Account1 account; public Person(Account1 account) { super(); this.account = account; } @Override publicvoid run() { account.withdraw(); } } |
执行结果: Thread-0:余额:1200 Thread-1:余额:400 最终余额:400 |
通过上面的两种方式,我们可以看到余额正常了。
转载于:https://blog.51cto.com/libaiqiang/1268748